diff --git a/nselib/afp.lua b/nselib/afp.lua index a8a23fd79..5ee74078e 100644 --- a/nselib/afp.lua +++ b/nselib/afp.lua @@ -36,24 +36,24 @@ -- -- The short version: -- --- helper = afp.Helper:new() --- status, response = helper:OpenSession( host, port ) --- status, response = helper:Login() --- .. do some fancy AFP stuff .. --- status, response = helper:Logout() --- status, response = helper:CloseSession() +-- helper = afp.Helper:new() +-- status, response = helper:OpenSession( host, port ) +-- status, response = helper:Login() +-- .. do some fancy AFP stuff .. +-- status, response = helper:Logout() +-- status, response = helper:CloseSession() -- -- -- Here's the longer version, with some explanatory text. To start using the Helper class, -- the script has to create it's own instance. We do this by issuing the following: -- --- helper = afp.Helper:new() +-- helper = afp.Helper:new() -- -- -- Next a session to the AFP server must be established, this is done using the OpenSession method of the -- Helper class, like this: -- --- status, response = helper:OpenSession( host, port ) +-- status, response = helper:OpenSession( host, port ) -- -- -- The next step needed to be performed is to authenticate to the server. We need to do this even for @@ -61,26 +61,26 @@ -- authenticate using nil for both username and password. This can be achieved by calling the Login method -- without any parameters, like this: -- --- status, response = helper:Login() +-- status, response = helper:Login() -- -- -- To authenticate to the server using the username 'admin' and password 'nimda' we do this instead: -- --- status, response = helper:Login('admin', 'nimda') +-- status, response = helper:Login('admin', 'nimda') -- -- -- At this stage we're authenticated and can call any of the AFP functions we're authorized to. -- For the purpose of this documentation, we will attempt to list the servers share points. -- We do this by issuing the following: -- --- status, shares = helper:ListShares() +-- status, shares = helper:ListShares() -- -- -- Once we're finnished, we need to logout and close the AFP session this is done by calling the -- following two methods of the Helper class: -- --- status, response = helper:Logout() --- status, response = helper:CloseSession() +-- status, response = helper:Logout() +-- status, response = helper:CloseSession() -- -- -- Consult the documentation of each function to learn more about their respective return values. @@ -109,7 +109,7 @@ -- -- Revised 03/09/2010 - v0.5 - documentation, documenation and more documentation -- Revised 04/03/2011 - v0.6 - add support for getting file- sizes, dates and Unix ACLs --- - moved afp.username & afp.password arguments to library +-- - moved afp.username & afp.password arguments to library local bin = require "bin" local bit = require "bit" @@ -124,311 +124,311 @@ local HAVE_SSL, openssl = pcall(require,'openssl') -- Table of valid REQUESTs local REQUEST = { - CloseSession = 0x01, - OpenSession = 0x04, - Command = 0x02, - GetStatus = 0x03, - Write = 0x06, + CloseSession = 0x01, + OpenSession = 0x04, + Command = 0x02, + GetStatus = 0x03, + Write = 0x06, } -- Table of headers flags to be set accordingly in requests and responses local FLAGS = { - Request = 0, - Response = 1 + Request = 0, + Response = 1 } -- Table of possible AFP_COMMANDs COMMAND = { - FPCloseVol = 0x02, - FPCloseFork = 0x04, - FPCopyFile = 0x05, - FPCreateDir = 0x06, - FPCreateFile = 0x07, - FPGetSrvrInfo = 0x0f, - FPGetSrvParms = 0x10, - FPLogin = 0x12, - FPLoginCont = 0x13, - FPLogout = 0x14, - FPMapId = 0x15, - FPMapName = 0x16, - FPGetUserInfo = 0x25, - FPOpenVol = 0x18, - FPOpenFork = 0x1a, - FPGetFileDirParams = 0x22, - FPChangePassword = 0x24, - FPReadExt = 0x3c, - FPWriteExt = 0x3d, - FPGetAuthMethods = 0x3e, - FPLoginExt = 0x3f, - FPEnumerateExt2 = 0x44, + FPCloseVol = 0x02, + FPCloseFork = 0x04, + FPCopyFile = 0x05, + FPCreateDir = 0x06, + FPCreateFile = 0x07, + FPGetSrvrInfo = 0x0f, + FPGetSrvParms = 0x10, + FPLogin = 0x12, + FPLoginCont = 0x13, + FPLogout = 0x14, + FPMapId = 0x15, + FPMapName = 0x16, + FPGetUserInfo = 0x25, + FPOpenVol = 0x18, + FPOpenFork = 0x1a, + FPGetFileDirParams = 0x22, + FPChangePassword = 0x24, + FPReadExt = 0x3c, + FPWriteExt = 0x3d, + FPGetAuthMethods = 0x3e, + FPLoginExt = 0x3f, + FPEnumerateExt2 = 0x44, } USER_BITMAP = { - UserId = 0x01, - PrimaryGroupId = 0x2, - UUID = 0x4 + UserId = 0x01, + PrimaryGroupId = 0x2, + UUID = 0x4 } VOL_BITMAP = { - Attributes = 0x1, - Signature = 0x2, - CreationDate = 0x4, - ModificationDate = 0x8, - BackupDate = 0x10, - ID = 0x20, - BytesFree = 0x40, - BytesTotal = 0x80, - Name = 0x100, - ExtendedBytesFree = 0x200, - ExtendedBytesTotal = 0x400, - BlockSize = 0x800 + Attributes = 0x1, + Signature = 0x2, + CreationDate = 0x4, + ModificationDate = 0x8, + BackupDate = 0x10, + ID = 0x20, + BytesFree = 0x40, + BytesTotal = 0x80, + Name = 0x100, + ExtendedBytesFree = 0x200, + ExtendedBytesTotal = 0x400, + BlockSize = 0x800 } FILE_BITMAP = { - Attributes = 0x1, - ParentDirId = 0x2, - CreationDate = 0x4, - ModificationDate = 0x8, - BackupDate = 0x10, - FinderInfo = 0x20, - LongName = 0x40, - ShortName = 0x80, - NodeId = 0x100, - DataForkSize = 0x200, - ResourceForkSize = 0x400, - ExtendedDataForkSize = 0x800, - LaunchLimit = 0x1000, - UTF8Name = 0x2000, - ExtendedResourceForkSize = 0x4000, - UnixPrivileges = 0x8000, - ALL = 0xFFFF + Attributes = 0x1, + ParentDirId = 0x2, + CreationDate = 0x4, + ModificationDate = 0x8, + BackupDate = 0x10, + FinderInfo = 0x20, + LongName = 0x40, + ShortName = 0x80, + NodeId = 0x100, + DataForkSize = 0x200, + ResourceForkSize = 0x400, + ExtendedDataForkSize = 0x800, + LaunchLimit = 0x1000, + UTF8Name = 0x2000, + ExtendedResourceForkSize = 0x4000, + UnixPrivileges = 0x8000, + ALL = 0xFFFF } DIR_BITMAP = { - Attributes = 0x1, - ParentDirId = 0x2, - CreationDate = 0x4, - ModificationDate = 0x8, - BackupDate = 0x10, - FinderInfo = 0x20, - LongName = 0x40, - ShortName = 0x80, - NodeId = 0x100, - OffspringCount = 0x200, - OwnerId = 0x400, - GroupId = 0x800, - AccessRights = 0x1000, - UTF8Name = 0x2000, - UnixPrivileges = 0x8000, - ALL = 0xBFFF, + Attributes = 0x1, + ParentDirId = 0x2, + CreationDate = 0x4, + ModificationDate = 0x8, + BackupDate = 0x10, + FinderInfo = 0x20, + LongName = 0x40, + ShortName = 0x80, + NodeId = 0x100, + OffspringCount = 0x200, + OwnerId = 0x400, + GroupId = 0x800, + AccessRights = 0x1000, + UTF8Name = 0x2000, + UnixPrivileges = 0x8000, + ALL = 0xBFFF, } PATH_TYPE = { - ShortName = 1, - LongName = 2, - UTF8Name = 3, + ShortName = 1, + LongName = 2, + UTF8Name = 3, } ACCESS_MODE = { - Read = 0x1, - Write = 0x2, - DenyRead = 0x10, - DenyWrite = 0x20 + Read = 0x1, + Write = 0x2, + DenyRead = 0x10, + DenyWrite = 0x20 } -- Access controls ACLS = { - OwnerSearch = 0x1, - OwnerRead = 0x2, - OwnerWrite = 0x4, + OwnerSearch = 0x1, + OwnerRead = 0x2, + OwnerWrite = 0x4, - GroupSearch = 0x100, - GroupRead = 0x200, - GroupWrite = 0x400, + GroupSearch = 0x100, + GroupRead = 0x200, + GroupWrite = 0x400, - EveryoneSearch = 0x10000, - EveryoneRead = 0x20000, - EveryoneWrite = 0x40000, + EveryoneSearch = 0x10000, + EveryoneRead = 0x20000, + EveryoneWrite = 0x40000, - UserSearch = 0x100000, - UserRead = 0x200000, - UserWrite = 0x400000, + UserSearch = 0x100000, + UserRead = 0x200000, + UserWrite = 0x400000, - BlankAccess = 0x10000000, - UserIsOwner = 0x80000000 + BlankAccess = 0x10000000, + UserIsOwner = 0x80000000 } -- User authentication modules UAM = { - NoUserAuth = "No User Authent", - ClearText = "Cleartxt Passwrd", - RandNum = "Randnum Exchange", - TwoWayRandNum = "2-Way Randnum", - DHCAST128 = "DHCAST128", - DHX2 = "DHX2", - Kerberos = "Client Krb v2", - Reconnect = "Recon1", + NoUserAuth = "No User Authent", + ClearText = "Cleartxt Passwrd", + RandNum = "Randnum Exchange", + TwoWayRandNum = "2-Way Randnum", + DHCAST128 = "DHCAST128", + DHX2 = "DHX2", + Kerberos = "Client Krb v2", + Reconnect = "Recon1", } ERROR = { - SocketError = 1000, - CustomError = 0xdeadbeef, + SocketError = 1000, + CustomError = 0xdeadbeef, - FPNoErr = 0, - FPAccessDenied = -5000, - FPAuthContinue = -5001, - FPBadUAM = -5002, - FPBadVersNum = -5003, - FPBitmapErr = - 5004, - FPCantMove = - 5005, - FPEOFErr = -5009, - FPItemNotFound = -5012, - FPLockErr = -5013, - FPMiscErr = -5014, - FPObjectExists = -5017, - FPObjectNotFound = -5018, - FPParamErr = -5019, - FPUserNotAuth = -5023, - FPCallNotSupported = -5024, + FPNoErr = 0, + FPAccessDenied = -5000, + FPAuthContinue = -5001, + FPBadUAM = -5002, + FPBadVersNum = -5003, + FPBitmapErr = - 5004, + FPCantMove = - 5005, + FPEOFErr = -5009, + FPItemNotFound = -5012, + FPLockErr = -5013, + FPMiscErr = -5014, + FPObjectExists = -5017, + FPObjectNotFound = -5018, + FPParamErr = -5019, + FPUserNotAuth = -5023, + FPCallNotSupported = -5024, } MAP_ID = { - UserIDToName = 1, - GroupIDToName = 2, - UserIDToUTF8Name = 3, - GroupIDToUTF8Name = 4, - UserUUIDToUTF8Name = 5, - GroupUUIDToUTF8Name = 6 + UserIDToName = 1, + GroupIDToName = 2, + UserIDToUTF8Name = 3, + GroupIDToUTF8Name = 4, + UserUUIDToUTF8Name = 5, + GroupUUIDToUTF8Name = 6 } MAP_NAME = { - NameToUserID = 1, - NameToGroupID = 2, - UTF8NameToUserID = 3, - UTF8NameToGroupID = 4, - UTF8NameToUserUUID = 5, - UTF8NameToGroupUUID = 6 + NameToUserID = 1, + NameToGroupID = 2, + UTF8NameToUserID = 3, + UTF8NameToGroupID = 4, + UTF8NameToUserUUID = 5, + UTF8NameToGroupUUID = 6 } SERVERFLAGS = { - CopyFile = 0x01, - ChangeablePasswords = 0x02, - NoPasswordSaving = 0x04, - ServerMessages = 0x08, - ServerSignature = 0x10, - TCPoverIP = 0x20, - ServerNotifications = 0x40, - Reconnect = 0x80, - OpenDirectory = 0x100, - UTF8ServerName = 0x200, - UUIDs = 0x400, - SuperClient = 0x8000 + CopyFile = 0x01, + ChangeablePasswords = 0x02, + NoPasswordSaving = 0x04, + ServerMessages = 0x08, + ServerSignature = 0x10, + TCPoverIP = 0x20, + ServerNotifications = 0x40, + Reconnect = 0x80, + OpenDirectory = 0x100, + UTF8ServerName = 0x200, + UUIDs = 0x400, + SuperClient = 0x8000 } local ERROR_MSG = { - [ERROR.FPAccessDenied]="Access Denied", - [ERROR.FPAuthContinue]="Authentication is not yet complete", - [ERROR.FPBadUAM]="Specified UAM is unknown", - [ERROR.FPBadVersNum]="Server does not support the specified AFP version", - [ERROR.FPBitmapErr]="Attempt was made to get or set a parameter that cannot be obtained or set with this command, or a required bitmap is null", - [ERROR.FPCantMove]="Attempt was made to move a directory into one of its descendent directories.", - [ERROR.FPEOFErr]="No more matches or end of fork reached.", - [ERROR.FPLockErr]="Some or all of the requested range is locked by another user; a lock range conflict exists.", - [ERROR.FPMiscErr]="Non-AFP error occurred.", - [ERROR.FPObjectNotFound]="Input parameters do not point to an existing directory, file, or volume.", - [ERROR.FPParamErr]="Parameter error.", - [ERROR.FPObjectExists] = "File or directory already exists.", - [ERROR.FPUserNotAuth] = "UAM failed (the specified old password doesn't match); no user is logged in yet for the specified session; authentication failed; password is incorrect.", - [ERROR.FPItemNotFound] = "Specified APPL mapping, comment, or icon was not found in the Desktop database; specified ID is unknown.", - [ERROR.FPCallNotSupported] = "Server does not support this command.", + [ERROR.FPAccessDenied]="Access Denied", + [ERROR.FPAuthContinue]="Authentication is not yet complete", + [ERROR.FPBadUAM]="Specified UAM is unknown", + [ERROR.FPBadVersNum]="Server does not support the specified AFP version", + [ERROR.FPBitmapErr]="Attempt was made to get or set a parameter that cannot be obtained or set with this command, or a required bitmap is null", + [ERROR.FPCantMove]="Attempt was made to move a directory into one of its descendent directories.", + [ERROR.FPEOFErr]="No more matches or end of fork reached.", + [ERROR.FPLockErr]="Some or all of the requested range is locked by another user; a lock range conflict exists.", + [ERROR.FPMiscErr]="Non-AFP error occurred.", + [ERROR.FPObjectNotFound]="Input parameters do not point to an existing directory, file, or volume.", + [ERROR.FPParamErr]="Parameter error.", + [ERROR.FPObjectExists] = "File or directory already exists.", + [ERROR.FPUserNotAuth] = "UAM failed (the specified old password doesn't match); no user is logged in yet for the specified session; authentication failed; password is incorrect.", + [ERROR.FPItemNotFound] = "Specified APPL mapping, comment, or icon was not found in the Desktop database; specified ID is unknown.", + [ERROR.FPCallNotSupported] = "Server does not support this command.", } -- Check if all the bits in flag are set in bitmap. local function flag_is_set(bitmap, flag) - return bit.band(bitmap, flag) == flag + return bit.band(bitmap, flag) == flag end -- Response class returned by all functions in Proto Response = { - new = function(self,o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self,o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, - --- Sets the error code - -- - -- @param code number containing the error code - setErrorCode = function( self, code ) - self.error_code = code - end, + --- Sets the error code + -- + -- @param code number containing the error code + setErrorCode = function( self, code ) + self.error_code = code + end, - --- Gets the error code - -- - -- @return code number containing the error code - getErrorCode = function( self ) - return self.error_code - end, + --- Gets the error code + -- + -- @return code number containing the error code + getErrorCode = function( self ) + return self.error_code + end, - --- Gets the error message - -- - -- @return msg string containing the error - getErrorMessage = function(self) - if self.error_msg then - return self.error_msg - else - return ERROR_MSG[self.error_code] or ("Unknown error (%d) occured"):format(self.error_code) - end - end, + --- Gets the error message + -- + -- @return msg string containing the error + getErrorMessage = function(self) + if self.error_msg then + return self.error_msg + else + return ERROR_MSG[self.error_code] or ("Unknown error (%d) occured"):format(self.error_code) + end + end, - --- Sets the error message - -- - -- @param msg string containing the error message - setErrorMessage = function(self, msg) - self.error_code = ERROR.CustomError - self.error_msg = msg - end, + --- Sets the error message + -- + -- @param msg string containing the error message + setErrorMessage = function(self, msg) + self.error_code = ERROR.CustomError + self.error_msg = msg + end, - --- Sets the result - -- - -- @param result result to set - setResult = function(self, result) - self.result = result - end, + --- Sets the result + -- + -- @param result result to set + setResult = function(self, result) + self.result = result + end, - --- Get the result - -- - -- @return result - getResult = function(self) - return self.result - end, + --- Get the result + -- + -- @return result + getResult = function(self) + return self.result + end, - --- Sets the packet - setPacket = function( self, packet ) - self.packet = packet - end, + --- Sets the packet + setPacket = function( self, packet ) + self.packet = packet + end, - getPacket = function( self ) - return self.packet - end, + getPacket = function( self ) + return self.packet + end, - --- Gets the packet data - getPacketData = function(self) - return self.packet.data - end, + --- Gets the packet data + getPacketData = function(self) + return self.packet.data + end, - --- Gets the packet header - getPacketHeader = function(self) - return self.packet.header - end, + --- Gets the packet header + getPacketHeader = function(self) + return self.packet.header + end, } --- Proto class containing all AFP specific code @@ -437,900 +437,900 @@ Response = { -- http://developer.apple.com/mac/library/documentation/Networking/Reference/AFP_Reference/Reference/reference.html Proto = { - RequestId = 1, - - new = function(self,o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o - end, - - setSocket = function(self, socket) - self.socket = socket - end, - - --- 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 - create_fp_packet = function( self, 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, self.RequestId, data_offset, data_len, reserved) - - self.RequestId = self.RequestId + 1 - return header .. data - 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 - parse_fp_header = function( self, 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) ) - - if header.error_code ~= 0 then - header.error_msg = ERROR_MSG[header.error_code] or ("Unknown error: %d"):format(header.error_code) - header.error_msg = "ERROR: " .. header.error_msg - end - header.raw = packet:sub(1,16) - return header - end, - - --- Reads a AFP packet of the socket - -- - -- @return Response object - read_fp_packet = function( self ) - - local packet = {} - local buf = "" - local status, response - - status, buf = self.socket:receive_bytes(16) - if ( not status ) then - response = Response:new() - response:setErrorCode(ERROR.SocketError) - response:setErrorMessage(buf) - return response - end - - packet.header = self:parse_fp_header( buf ) - while buf:len() < packet.header.length + packet.header.raw:len() do - local tmp - status, tmp = self.socket:receive_bytes( packet.header.length + 16 - buf:len() ) - if not status then - response = Response:new() - response:setErrorCode(ERROR.SocketError) - response:setErrorMessage(buf) - return response - end - buf = buf .. tmp - end - - packet.data = buf:len() > 16 and buf:sub( 17 ) or "" - response = Response:new() - response:setErrorCode(packet.header.error_code) - response:setPacket(packet) - - return response - end, - - --- Sends the raw packet over the socket - -- - -- @param packet containing the raw data - -- @return Response object - send_fp_packet = function( self, packet ) - return self.socket:send(packet) - end, - - --- Sends an DSIOpenSession request to the server and handles the response - -- - -- @return Response object - dsi_open_session = function( self, host, port ) - local data_offset = 0 - local option = 0x01 -- Attention Quantum - local option_len = 4 - local quantum = 1024 - local data, packet, status - - data = bin.pack( "CCI", option, option_len, quantum ) - packet = self:create_fp_packet( REQUEST.OpenSession, data_offset, data ) - - self:send_fp_packet( packet ) - return self:read_fp_packet() - end, - - --- Sends an DSICloseSession request to the server and handles the response - dsi_close_session = function( self ) - local data_offset = 0 - local option = 0x01 -- Attention Quantum - local option_len = 4 - local quantum = 1024 - local data, packet, status - - data = "" - packet = self:create_fp_packet( REQUEST.CloseSession, data_offset, data ) - - self:send_fp_packet( packet ) - end, - - -- Sends an FPCopyFile request to the server - -- - -- @param src_vol number containing the ID of the src file volume - -- @param srd_did number containing the directory id of the src file - -- @param src_path string containingt the file path/name of the src file - -- @param dst_vol number containing the ID of the dst file volume - -- @param dst_did number containing the id of the dest. directory - -- @param dst_path string containing the dest path (can be nil or "") - -- @param new_name string containign the new name of the destination - -- @return Response object - fp_copy_file = function(self, src_vol, src_did, src_path, dst_vol, dst_did, dst_path, new_name ) - local pad, data_offset = 0, 0 - local unicode_names, unicode_hint = 0x03, 0x08000103 - local data, packet, response - - -- make sure we have empty names rather than nil values - local dst_path = dst_path or "" - local src_path = src_path or "" - local new_name = new_name or "" - - data = bin.pack(">CCSISI", COMMAND.FPCopyFile, pad, src_vol, src_did, dst_vol, dst_did ) - data = data .. bin.pack(">CIP", unicode_names, unicode_hint, src_path ) - data = data .. bin.pack(">CIP", unicode_names, unicode_hint, dst_path ) - data = data .. bin.pack(">CIP", unicode_names, unicode_hint, new_name ) - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet() - end, - - --- Sends an GetStatus DSI request (which is basically a FPGetSrvrInfo - -- AFP request) to the server and handles the response - -- - -- @return status (true or false) - -- @return table with server information (if status is true) or error string - -- (if status is false) - fp_get_server_info = function(self) - local packet - local data_offset = 0 - local pad = 0 - local response, result = {}, {} - local offsets = {} - local pos - local _ - local status - - local data = bin.pack("CC", COMMAND.FPGetSrvrInfo, 0) - packet = self:create_fp_packet(REQUEST.GetStatus, data_offset, data) - self:send_fp_packet(packet) - response = self:read_fp_packet() - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - packet = response.packet - - -- parse and store the offsets in the 'header' - pos, offsets.machine_type, offsets.afp_version_count, - offsets.uam_count, offsets.volume_icon_and_mask - = bin.unpack(">SSSS", packet.data, pos) - - -- the flags are directly in the 'header' - result.flags = {} - pos, result.flags.raw = bin.unpack(">S", packet.data, pos) - - -- the short server name is stored directly in the 'header' as - -- well - pos, result.server_name = bin.unpack("p", packet.data, pos) - - -- Server offset should begin at an even boundary see link below - -- http://developer.apple.com/mac/library/documentation/Networking/Reference/AFP_Reference/Reference/reference.html#//apple_ref/doc/uid/TP40003548-CH3-CHDIEGED - if (pos + 1) % 2 ~= 0 then - pos = pos + 1 - end - - -- and some more offsets - pos, offsets.server_signature, offsets.network_addresses_count, - offsets.directory_names_count, offsets.utf8_server_name - = bin.unpack(">SSSS", packet.data, pos) - - -- this sets up all the server flaqs in the response table as booleans - result.flags.SuperClient = flag_is_set(result.flags.raw, SERVERFLAGS.SuperClient) - result.flags.UUIDs = flag_is_set(result.flags.raw, SERVERFLAGS.UUIDs) - result.flags.UTF8ServerName = flag_is_set(result.flags.raw, SERVERFLAGS.UTF8ServerName) - result.flags.OpenDirectory = flag_is_set(result.flags.raw, SERVERFLAGS.OpenDirectory) - result.flags.Reconnect = flag_is_set(result.flags.raw, SERVERFLAGS.Reconnect) - result.flags.ServerNotifications = flag_is_set(result.flags.raw, SERVERFLAGS.ServerNotifications) - result.flags.TCPoverIP = flag_is_set(result.flags.raw, SERVERFLAGS.TCPoverIP) - result.flags.ServerSignature = flag_is_set(result.flags.raw, SERVERFLAGS.ServerSignature) - result.flags.ServerMessages = flag_is_set(result.flags.raw, SERVERFLAGS.ServerMessages) - result.flags.NoPasswordSaving = flag_is_set(result.flags.raw, SERVERFLAGS.NoPasswordSaving) - result.flags.ChangeablePasswords = flag_is_set(result.flags.raw, SERVERFLAGS.ChangeablePasswords) - result.flags.CopyFile = flag_is_set(result.flags.raw, SERVERFLAGS.CopyFile) - - -- store the machine type - _, result.machine_type = bin.unpack("p", packet.data, offsets.machine_type + 1) - - -- this tells us the number of afp versions supported - pos, result.afp_version_count = bin.unpack("C", packet.data, offsets.afp_version_count + 1) - - -- now we loop through them all, storing for the response - result.afp_versions = {} - for i = 1,result.afp_version_count do - pos, _ = bin.unpack("p", packet.data, pos) - table.insert(result.afp_versions, _) - end - - -- same idea as the afp versions here - pos, result.uam_count = bin.unpack("C", packet.data, offsets.uam_count + 1) - - result.uams = {} - for i = 1,result.uam_count do - pos, _ = bin.unpack("p", packet.data, pos) - table.insert(result.uams, _) - end - - -- volume_icon_and_mask would normally be parsed out here, - -- however the apple docs say it is deprecated in Mac OS X, so - -- we don't bother with it - - -- server signature is 16 bytes - result.server_signature = string.sub(packet.data, offsets.server_signature + 1, offsets.server_signature + 16) - - -- this is the same idea as afp_version and uam above - pos, result.network_addresses_count = bin.unpack("C", packet.data, offsets.network_addresses_count + 1) - - result.network_addresses = {} - - -- gets a little complicated in here, basically each entry has - -- a length byte, a tag byte, and then the data. We parse - -- differently based on the tag - for i = 1, result.network_addresses_count do - local length - local tag - - pos, length = bin.unpack("C", packet.data, pos) - pos, tag = bin.unpack("C", packet.data, pos) - - if tag == 0x00 then - -- reserved, shouldn't ever come up, maybe this should - -- return an error? maybe not, lets just ignore this - elseif tag == 0x01 then - -- four byte ip - local octet = {} - pos, octet[1], octet[2], octet[3], octet[4] = bin.unpack("CCCC", packet.data, pos) - table.insert(result.network_addresses, string.format("%d.%d.%d.%d", octet[1], octet[2], octet[3], octet[4])) - elseif tag == 0x02 then - -- four byte ip and two byte port - local octet = {} - local port - pos, octet[1], octet[2], octet[3], octet[4], port = bin.unpack(">CCCCS", packet.data, pos) - table.insert(result.network_addresses, string.format("%d.%d.%d.%d:%d", octet[1], octet[2], octet[3], octet[4], port)) - elseif tag == 0x03 then - -- ddp address (two byte network, one byte - -- node, one byte socket) not tested, anyone - -- use ddp anymore? - local network - local node - local socket - pos, network = bin.unpack(">S", packet.data, pos) - pos, node = bin.unpack("C", packet.data, pos) - pos, socket = bin.unpack("C", packet.data, pos) - table.insert(result.network_addresses, string.format("ddp %d.%d:%d", network, node, socket)) - elseif tag == 0x04 then - -- dns name (string) - local temp - pos, temp = bin.unpack("z", packet.data:sub(1,pos+length-3), pos) - table.insert(result.network_addresses, temp) - elseif tag == 0x05 then - -- four byte ip and two byte port, client - -- should use ssh. not tested, should work as it - -- is the same as tag 0x02 - local octet = {} - local port - pos, octet[1], octet[2], octet[3], octet[4], port = bin.unpack(">CCCCS", packet.data, pos) - table.insert(result.network_addresses, string.format("ssh://%d.%d.%d.%d:%d", octet[1], octet[2], octet[3], octet[4], port)) - elseif tag == 0x06 then - -- 16 byte ipv6 - -- not tested, but should work (next tag is - -- tested) - local octet = {} - local j - local addr - - for j = 1, 8 do - pos, octet[j] = bin.unpack(">S", packet.data, pos) - end - - for j = 1, 7 do - addr = addr .. string.format("%04x:", octet[j]) - end - addr = addr .. string.format("%04x", octet[8]) - - table.insert(result.network_addresses, addr) - elseif tag == 0x07 then - -- 16 byte ipv6 and two byte port - local octet = {} - local port - local j - local addr - - for j = 1, 8 do - pos, octet[j] = bin.unpack(">S", packet.data, pos) - end - pos, port = bin.unpack(">S", packet.data, pos) - - addr = "[" - - for j = 1, 7 do - addr = addr .. string.format("%04x:", octet[j]) - end - addr = addr .. string.format("%04x]:%d", octet[8], port) - - table.insert(result.network_addresses, addr) - end - end - - -- same idea as the others here - pos, result.directory_names_count = bin.unpack("C", packet.data, offsets.directory_names_count + 1) - - result.directory_names = {} - for i = 1, result.directory_names_count do - local dirname - pos, dirname = bin.unpack("p", packet.data, pos) - table.insert(result.directory_names, dirname) - end - - -- only one utf8 server name. note this string has a two-byte length. - _, result.utf8_server_name = bin.unpack(">P", packet.data, offsets.utf8_server_name + 1) - response.result = result - - return response - end, - - - --- Sends an FPGetUserInfo AFP request to the server and handles the response - -- - -- @return response object with the following result user_bitmap and - -- uid fields - fp_get_user_info = function( self ) - - local packet, pos, status, response - local data_offset = 0 - local flags = 1 -- Default User - local uid = 0 - local bitmap = USER_BITMAP.UserId - local result = {} - - local data = bin.pack( "CCI>S", COMMAND.FPGetUserInfo, flags, uid, bitmap ) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - - self:send_fp_packet( packet ) - response = self:read_fp_packet() - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - pos, response.result.user_bitmap, response.result.uid = bin.unpack(">S>I", packet.data) - - return response - end, - - --- Sends an FPGetSrvrParms AFP request to the server and handles the response - -- - -- @return response object with the following result server_time, - -- vol_count, volumes fields - fp_get_srvr_parms = function(self) - local packet, status, data - local data_offset = 0 - local response = {} - local pos = 0 - local parms = {} - - data = bin.pack("CC", COMMAND.FPGetSrvParms, 0) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet() - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - data = response:getPacketData() - pos, parms.server_time, parms.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 - - parms.volumes = {} - - for i=1, parms.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(parms.volumes, string.format("%s", volume_name) ) - end - - response:setResult(parms) - - return 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 currently supports the following authentication methods: - -- o No User Authent - -- o DHCAST128 - -- - -- The DHCAST128 UAM should work against most servers even though it's - -- superceeded by the DHX2 UAM. - -- - -- @param afp_version string (AFP3.3|AFP3.2|AFP3.1) - -- @param uam string containing authentication information - -- @return Response object - fp_login = function( self, afp_version, uam, username, password, options ) - local packet, status, data - local data_offset = 0 - local status, response - - if not HAVE_SSL then - response = Response:new() - response:setErrorMessage("OpenSSL not available, aborting ...") - return response - end - - -- 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 - response = Response:new() - response:setErrorMessage("Incorrect AFP version") - return response - end - - if ( uam == "No User Authent" ) then - data = bin.pack( "CCACA", COMMAND.FPLogin, afp_version:len(), afp_version, uam:len(), uam ) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet( ) - elseif( uam == "DHCAST128" ) then - local dhx_s2civ, dhx_c2civ = 'CJalbert', 'LWallace' - local p, g, Ra, Ma, Mb, K, nonce - local EncData, PlainText, K_bin, auth_response - local _, Id - local username = username or "" - local password = password or "" - - if ( bit.mod(username:len(), 2) == 0 ) then - username = username .. string.char(0) - end - - p = openssl.bignum_hex2bn("BA2873DFB06057D43F2024744CEEE75B") - g = openssl.bignum_dec2bn("7") - Ra = openssl.bignum_hex2bn("86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42") - Ma = openssl.bignum_mod_exp(g, Ra, p) - - data = bin.pack( "CpppA", COMMAND.FPLogin, afp_version, uam, username, openssl.bignum_bn2bin(Ma) ) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet( ) - if ( response:getErrorCode() ~= ERROR.FPAuthContinue ) then - return response - end - - if ( response.packet.header.length ~= 50 ) then - response:setErrorMessage("LoginContinue packet contained invalid data") - return response - end - - _, Id, Mb, EncData = bin.unpack(">SH16A32", response.packet.data ) - - Mb = openssl.bignum_hex2bn( Mb ) - K = openssl.bignum_mod_exp (Mb, Ra, p) - K_bin = openssl.bignum_bn2bin(K) - nonce = openssl.decrypt("cast5-cbc", K_bin, dhx_s2civ, EncData, false ):sub(1,16) - nonce = openssl.bignum_add( openssl.bignum_bin2bn(nonce), openssl.bignum_dec2bn("1") ) - PlainText = openssl.bignum_bn2bin(nonce) .. Util.ZeroPad(password, 64) - auth_response = openssl.encrypt( "cast5-cbc", K_bin, dhx_c2civ, PlainText, true) - - data = bin.pack( "CC>SA", COMMAND.FPLoginCont, 0, Id, auth_response ) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet( ) - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return response - end - return response - end - response:setErrorMessage("Unsupported uam: " .. uam or "nil") - return response - end, - - -- Terminates sessions and frees server resources established by FPLoginand FPLoginExt. - -- - -- @return response object - fp_logout = function( self ) - local packet, data, response - local data_offset, pad = 0, 0 - - data = bin.pack("CC", COMMAND.FPLogout, pad) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet( ) - end, - - --- Sends an FPOpenVol request to the server and handles the response - -- - -- @param bitmap number bitmask of volume information to request - -- @param volume_name string containing the volume name to query - -- @return response object with the following result bitmap and - -- volume_id fields - fp_open_vol = function( self, bitmap, volume_name ) - local packet, status, pos, data - local data_offset, pad = 0, 0 - local response, volume = {}, {} - - data = bin.pack("CC>SCA", COMMAND.FPOpenVol, pad, bitmap, volume_name:len(), volume_name ) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet() - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - pos, volume.bitmap, volume.volume_id = bin.unpack(">S>S", response.packet.data) - response:setResult(volume) - return response - end, - - - --- Sends an FPGetFileDirParms request to the server and handles the response - -- - -- @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 response object with the following result file_bitmap, dir_bitmap, - -- file_type and (dir or file tables) depending on whether - -- did is a file or directory - fp_get_file_dir_parms = function( self, volume_id, did, file_bitmap, dir_bitmap, path ) - - local packet, status, data - local data_offset = 0 - local pad = 0 - local response, parms = {}, {} - local pos - - if ( did == nil ) then - response = Response:new() - response:setErrorMessage("No Directory Id supplied") - return response - end - - if ( volume_id == nil ) then - response = Response:new() - response:setErrorMessage("No Volume Id supplied") - return response - end - - 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 = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet() - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - pos, parms.file_bitmap, parms.dir_bitmap, parms.file_type, pad = bin.unpack( ">S>SCC", response.packet.data ) - - -- file or dir? - if ( parms.file_type == 0x80 ) then - pos, parms.dir = Util.decode_dir_bitmap( parms.dir_bitmap, response.packet.data, pos ) - else - -- file - pos, parms.file = Util.decode_file_bitmap( parms.file_bitmap, response.packet.data, pos ) - end - - response:setResult(parms) - return response - end, - - --- Sends an FPEnumerateExt2 request to the server and handles the response - -- - -- @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 response object with the following result set to a table of tables containing - -- file_bitmap, dir_bitmap, req_count fields - fp_enumerate_ext2 = function( self, volume_id, did, file_bitmap, dir_bitmap, req_count, start_index, reply_size, path ) - - local packet, pos, _, status - local data_offset = 0 - local pad = 0 - local response,records = {}, {} - - 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>ICCA", req_count, start_index, reply_size, path.type, path.len, path.name ) - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - - self:send_fp_packet( packet ) - response = self:read_fp_packet( ) - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - pos, file_bitmap, dir_bitmap, req_count = bin.unpack(">S>S>S", response.packet.data) - - records = {} - - for i=1, req_count do - local record = {} - local len, _, ftype - - pos, len, ftype, _ = bin.unpack(">SCC", response.packet.data, pos) - - if ( ftype == 0x80 ) then - _, record = Util.decode_dir_bitmap( dir_bitmap, response.packet.data, pos ) - else - -- file - _, record = Util.decode_file_bitmap( file_bitmap, response.packet.data, pos ) - end - - if bit.mod( len, 2 ) ~= 0 then - len = len + 1 - end - - pos = pos + ( len - 4 ) - - record.type = ftype - table.insert(records, record) - end - - response:setResult(records) - return response - end, - - --- Sends an FPOpenFork request to the server and handles the response - -- - -- @param flag 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 response object with the following result contents file_bitmap and fork_id - fp_open_fork = function( self, flag, volume_id, did, file_bitmap, access_mode, path ) - - local packet, _ - local data_offset = 0 - local pad = 0 - local response, fork = {}, {} - - local data = bin.pack( "CC>S>I>S>S", COMMAND.FPOpenFork, flag, volume_id, did, file_bitmap, access_mode ) - - if path.type == PATH_TYPE.LongName then - data = data .. bin.pack( "CCA", path.type, path.len, path.name ) - end - - if path.type == PATH_TYPE.UTF8Name then - local unicode_hint = 0x08000103 - data = data .. bin.pack( "C>I>SA", path.type, unicode_hint, path.len, path.name ) - end - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet() - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - _, fork.file_bitmap, fork.fork_id = bin.unpack(">S>S", response.packet.data) - response:setResult(fork) - return response - end, - - --- FPCloseFork - -- - -- @param fork number containing the fork to close - -- @return response object - fp_close_fork = function( self, fork ) - local packet - local data_offset = 0 - local pad = 0 - local response = {} - - local data = bin.pack( "CC>S", COMMAND.FPCloseFork, pad, fork ) - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet( ) - end, - - --- FPCreateDir - -- - -- @param vol_id number containing the volume id - -- @param dir_id number containing the directory id - -- @param path string containing the name of the directory - -- @return response object - fp_create_dir = function( self, vol_id, dir_id, path ) - local packet - local data_offset, pad = 0, 0 - local response = {} - - local data = bin.pack( "CC>S>ICp", COMMAND.FPCreateDir, pad, vol_id, dir_id, path.type, path.name ) - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet( ) - end, - - --- Sends an FPCloseVol request to the server and handles the response - -- - -- @param volume_id number containing the id of the volume to close - -- @return response object - fp_close_vol = function( self, volume_id ) - local packet - local data_offset, pad = 0, 0 - local response = {} - - local data = bin.pack( "CC>S", COMMAND.FPCloseVol, pad, volume_id ) - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet( ) - end, - - --- FPReadExt - -- - -- @param fork number containing the open fork - -- @param offset number containing the offset from where writing should start. Negative value indicates offset from the end of the fork - -- @param count number containing the number of bytes to be written - -- @return response object - fp_read_ext = function( self, fork, offset, count ) - local pad = 0 - local packet, response - local data_offset = 0 - local block_size = 1024 - local data = bin.pack( "CC>S>L>L", COMMAND.FPReadExt, pad, fork, offset, count ) - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet( ) - - if ( response:getErrorCode() == ERROR.FPEOFErr and response.packet.header.length > 0 ) then - response:setErrorCode( ERROR.FPNoErr ) - end - - response:setResult( response.packet.data ) - return response - end, - - --- FPWriteExt - -- - -- @param flag number indicates whether Offset is relative to the beginning or end of the fork. - -- @param fork number containing the open fork - -- @param offset number containing the offset from where writing should start. Negative value indicates offset from the end of the fork - -- @param count number containing the number of bytes to be written - -- @param fdata string containing the data to be written - -- @return response object - fp_write_ext = function( self, flag, fork, offset, count, fdata ) - local packet - local data_offset = 20 - local data - - if count > fdata:len() then - local err = Response:new() - err:setErrorMessage("fp_write_ext: Count is greater than the amount of data") - return err - end - if count < 0 then - local err = Response:new() - err:setErrorMessage("fp_write_ext: Count must exceed zero") - return err - end - - data = bin.pack( "CC>S>L>LA", COMMAND.FPWriteExt, flag, fork, offset, count, fdata ) - packet = self:create_fp_packet( REQUEST.Write, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet( ) - end, - - --- FPCreateFile - -- - -- @param flag number where 0 indicates a soft create and 1 indicates a hard create. - -- @param vol_id number containing the volume id - -- @param did number containing the ancestor directory id - -- @param path string containing the path, including the volume, path and file name - -- @return response object - fp_create_file = function(self, flag, vol_id, did, path ) - local packet - local data_offset = 0 - local data = bin.pack( "CC>S>ICCA" , COMMAND.FPCreateFile, flag, vol_id, did, path.type, path.len, path.name ) - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - return self:read_fp_packet() - end, - - --- FPMapId - -- - -- @param subfunc number containing the subfunction to call - -- @param id number containing th id to translate - -- @return response object with the id in the result field - fp_map_id = function( self, subfunc, id ) - local packet, response - local data_offset = 0 - local data = bin.pack( "CC", COMMAND.FPMapId, subfunc ) - local _, len - - if ( subfunc == MAP_ID.UserUUIDToUTF8Name or subfunc == MAP_ID.GroupUUIDToUTF8Name ) then - data = data .. bin.pack(">L", id) - else - data = data .. bin.pack(">I", id) - end - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet( ) - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - -- Netatalk returns the name with 1-byte length prefix, - -- Mac OS has a 2-byte (UTF-8) length prefix - local _, len = bin.unpack("C", response.packet.data) - - -- if length is zero assume 2-byte length (UTF-8 name) - if len == 0 then - response:setResult( select(2, bin.unpack(">P", response.packet.data )) ) - else - response:setResult( select(2, bin.unpack("p", response.packet.data )) ) - end - return response - end, - - --- FPMapName - -- - -- @param subfunc number containing the subfunction to call - -- @param name string containing name to map - -- @return response object with the mapped name in the result field - fp_map_name = function( self, subfunc, name ) - local packet - local data_offset = 0 - local data = bin.pack( "CC>SA", COMMAND.FPMapName, subfunc, name:len(), name ) - local response - - packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) - self:send_fp_packet( packet ) - response = self:read_fp_packet( ) - - if response:getErrorCode() ~= ERROR.FPNoErr then - return response - end - - response:setResult( select(2, bin.unpack(">I", response.packet.data))) - return response - end, + RequestId = 1, + + new = function(self,o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, + + setSocket = function(self, socket) + self.socket = socket + end, + + --- 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 + create_fp_packet = function( self, 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, self.RequestId, data_offset, data_len, reserved) + + self.RequestId = self.RequestId + 1 + return header .. data + 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 + parse_fp_header = function( self, 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) ) + + if header.error_code ~= 0 then + header.error_msg = ERROR_MSG[header.error_code] or ("Unknown error: %d"):format(header.error_code) + header.error_msg = "ERROR: " .. header.error_msg + end + header.raw = packet:sub(1,16) + return header + end, + + --- Reads a AFP packet of the socket + -- + -- @return Response object + read_fp_packet = function( self ) + + local packet = {} + local buf = "" + local status, response + + status, buf = self.socket:receive_bytes(16) + if ( not status ) then + response = Response:new() + response:setErrorCode(ERROR.SocketError) + response:setErrorMessage(buf) + return response + end + + packet.header = self:parse_fp_header( buf ) + while buf:len() < packet.header.length + packet.header.raw:len() do + local tmp + status, tmp = self.socket:receive_bytes( packet.header.length + 16 - buf:len() ) + if not status then + response = Response:new() + response:setErrorCode(ERROR.SocketError) + response:setErrorMessage(buf) + return response + end + buf = buf .. tmp + end + + packet.data = buf:len() > 16 and buf:sub( 17 ) or "" + response = Response:new() + response:setErrorCode(packet.header.error_code) + response:setPacket(packet) + + return response + end, + + --- Sends the raw packet over the socket + -- + -- @param packet containing the raw data + -- @return Response object + send_fp_packet = function( self, packet ) + return self.socket:send(packet) + end, + + --- Sends an DSIOpenSession request to the server and handles the response + -- + -- @return Response object + dsi_open_session = function( self, host, port ) + local data_offset = 0 + local option = 0x01 -- Attention Quantum + local option_len = 4 + local quantum = 1024 + local data, packet, status + + data = bin.pack( "CCI", option, option_len, quantum ) + packet = self:create_fp_packet( REQUEST.OpenSession, data_offset, data ) + + self:send_fp_packet( packet ) + return self:read_fp_packet() + end, + + --- Sends an DSICloseSession request to the server and handles the response + dsi_close_session = function( self ) + local data_offset = 0 + local option = 0x01 -- Attention Quantum + local option_len = 4 + local quantum = 1024 + local data, packet, status + + data = "" + packet = self:create_fp_packet( REQUEST.CloseSession, data_offset, data ) + + self:send_fp_packet( packet ) + end, + + -- Sends an FPCopyFile request to the server + -- + -- @param src_vol number containing the ID of the src file volume + -- @param srd_did number containing the directory id of the src file + -- @param src_path string containingt the file path/name of the src file + -- @param dst_vol number containing the ID of the dst file volume + -- @param dst_did number containing the id of the dest. directory + -- @param dst_path string containing the dest path (can be nil or "") + -- @param new_name string containign the new name of the destination + -- @return Response object + fp_copy_file = function(self, src_vol, src_did, src_path, dst_vol, dst_did, dst_path, new_name ) + local pad, data_offset = 0, 0 + local unicode_names, unicode_hint = 0x03, 0x08000103 + local data, packet, response + + -- make sure we have empty names rather than nil values + local dst_path = dst_path or "" + local src_path = src_path or "" + local new_name = new_name or "" + + data = bin.pack(">CCSISI", COMMAND.FPCopyFile, pad, src_vol, src_did, dst_vol, dst_did ) + data = data .. bin.pack(">CIP", unicode_names, unicode_hint, src_path ) + data = data .. bin.pack(">CIP", unicode_names, unicode_hint, dst_path ) + data = data .. bin.pack(">CIP", unicode_names, unicode_hint, new_name ) + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet() + end, + + --- Sends an GetStatus DSI request (which is basically a FPGetSrvrInfo + -- AFP request) to the server and handles the response + -- + -- @return status (true or false) + -- @return table with server information (if status is true) or error string + -- (if status is false) + fp_get_server_info = function(self) + local packet + local data_offset = 0 + local pad = 0 + local response, result = {}, {} + local offsets = {} + local pos + local _ + local status + + local data = bin.pack("CC", COMMAND.FPGetSrvrInfo, 0) + packet = self:create_fp_packet(REQUEST.GetStatus, data_offset, data) + self:send_fp_packet(packet) + response = self:read_fp_packet() + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + packet = response.packet + + -- parse and store the offsets in the 'header' + pos, offsets.machine_type, offsets.afp_version_count, + offsets.uam_count, offsets.volume_icon_and_mask + = bin.unpack(">SSSS", packet.data, pos) + + -- the flags are directly in the 'header' + result.flags = {} + pos, result.flags.raw = bin.unpack(">S", packet.data, pos) + + -- the short server name is stored directly in the 'header' as + -- well + pos, result.server_name = bin.unpack("p", packet.data, pos) + + -- Server offset should begin at an even boundary see link below + -- http://developer.apple.com/mac/library/documentation/Networking/Reference/AFP_Reference/Reference/reference.html#//apple_ref/doc/uid/TP40003548-CH3-CHDIEGED + if (pos + 1) % 2 ~= 0 then + pos = pos + 1 + end + + -- and some more offsets + pos, offsets.server_signature, offsets.network_addresses_count, + offsets.directory_names_count, offsets.utf8_server_name + = bin.unpack(">SSSS", packet.data, pos) + + -- this sets up all the server flaqs in the response table as booleans + result.flags.SuperClient = flag_is_set(result.flags.raw, SERVERFLAGS.SuperClient) + result.flags.UUIDs = flag_is_set(result.flags.raw, SERVERFLAGS.UUIDs) + result.flags.UTF8ServerName = flag_is_set(result.flags.raw, SERVERFLAGS.UTF8ServerName) + result.flags.OpenDirectory = flag_is_set(result.flags.raw, SERVERFLAGS.OpenDirectory) + result.flags.Reconnect = flag_is_set(result.flags.raw, SERVERFLAGS.Reconnect) + result.flags.ServerNotifications = flag_is_set(result.flags.raw, SERVERFLAGS.ServerNotifications) + result.flags.TCPoverIP = flag_is_set(result.flags.raw, SERVERFLAGS.TCPoverIP) + result.flags.ServerSignature = flag_is_set(result.flags.raw, SERVERFLAGS.ServerSignature) + result.flags.ServerMessages = flag_is_set(result.flags.raw, SERVERFLAGS.ServerMessages) + result.flags.NoPasswordSaving = flag_is_set(result.flags.raw, SERVERFLAGS.NoPasswordSaving) + result.flags.ChangeablePasswords = flag_is_set(result.flags.raw, SERVERFLAGS.ChangeablePasswords) + result.flags.CopyFile = flag_is_set(result.flags.raw, SERVERFLAGS.CopyFile) + + -- store the machine type + _, result.machine_type = bin.unpack("p", packet.data, offsets.machine_type + 1) + + -- this tells us the number of afp versions supported + pos, result.afp_version_count = bin.unpack("C", packet.data, offsets.afp_version_count + 1) + + -- now we loop through them all, storing for the response + result.afp_versions = {} + for i = 1,result.afp_version_count do + pos, _ = bin.unpack("p", packet.data, pos) + table.insert(result.afp_versions, _) + end + + -- same idea as the afp versions here + pos, result.uam_count = bin.unpack("C", packet.data, offsets.uam_count + 1) + + result.uams = {} + for i = 1,result.uam_count do + pos, _ = bin.unpack("p", packet.data, pos) + table.insert(result.uams, _) + end + + -- volume_icon_and_mask would normally be parsed out here, + -- however the apple docs say it is deprecated in Mac OS X, so + -- we don't bother with it + + -- server signature is 16 bytes + result.server_signature = string.sub(packet.data, offsets.server_signature + 1, offsets.server_signature + 16) + + -- this is the same idea as afp_version and uam above + pos, result.network_addresses_count = bin.unpack("C", packet.data, offsets.network_addresses_count + 1) + + result.network_addresses = {} + + -- gets a little complicated in here, basically each entry has + -- a length byte, a tag byte, and then the data. We parse + -- differently based on the tag + for i = 1, result.network_addresses_count do + local length + local tag + + pos, length = bin.unpack("C", packet.data, pos) + pos, tag = bin.unpack("C", packet.data, pos) + + if tag == 0x00 then + -- reserved, shouldn't ever come up, maybe this should + -- return an error? maybe not, lets just ignore this + elseif tag == 0x01 then + -- four byte ip + local octet = {} + pos, octet[1], octet[2], octet[3], octet[4] = bin.unpack("CCCC", packet.data, pos) + table.insert(result.network_addresses, string.format("%d.%d.%d.%d", octet[1], octet[2], octet[3], octet[4])) + elseif tag == 0x02 then + -- four byte ip and two byte port + local octet = {} + local port + pos, octet[1], octet[2], octet[3], octet[4], port = bin.unpack(">CCCCS", packet.data, pos) + table.insert(result.network_addresses, string.format("%d.%d.%d.%d:%d", octet[1], octet[2], octet[3], octet[4], port)) + elseif tag == 0x03 then + -- ddp address (two byte network, one byte + -- node, one byte socket) not tested, anyone + -- use ddp anymore? + local network + local node + local socket + pos, network = bin.unpack(">S", packet.data, pos) + pos, node = bin.unpack("C", packet.data, pos) + pos, socket = bin.unpack("C", packet.data, pos) + table.insert(result.network_addresses, string.format("ddp %d.%d:%d", network, node, socket)) + elseif tag == 0x04 then + -- dns name (string) + local temp + pos, temp = bin.unpack("z", packet.data:sub(1,pos+length-3), pos) + table.insert(result.network_addresses, temp) + elseif tag == 0x05 then + -- four byte ip and two byte port, client + -- should use ssh. not tested, should work as it + -- is the same as tag 0x02 + local octet = {} + local port + pos, octet[1], octet[2], octet[3], octet[4], port = bin.unpack(">CCCCS", packet.data, pos) + table.insert(result.network_addresses, string.format("ssh://%d.%d.%d.%d:%d", octet[1], octet[2], octet[3], octet[4], port)) + elseif tag == 0x06 then + -- 16 byte ipv6 + -- not tested, but should work (next tag is + -- tested) + local octet = {} + local j + local addr + + for j = 1, 8 do + pos, octet[j] = bin.unpack(">S", packet.data, pos) + end + + for j = 1, 7 do + addr = addr .. string.format("%04x:", octet[j]) + end + addr = addr .. string.format("%04x", octet[8]) + + table.insert(result.network_addresses, addr) + elseif tag == 0x07 then + -- 16 byte ipv6 and two byte port + local octet = {} + local port + local j + local addr + + for j = 1, 8 do + pos, octet[j] = bin.unpack(">S", packet.data, pos) + end + pos, port = bin.unpack(">S", packet.data, pos) + + addr = "[" + + for j = 1, 7 do + addr = addr .. string.format("%04x:", octet[j]) + end + addr = addr .. string.format("%04x]:%d", octet[8], port) + + table.insert(result.network_addresses, addr) + end + end + + -- same idea as the others here + pos, result.directory_names_count = bin.unpack("C", packet.data, offsets.directory_names_count + 1) + + result.directory_names = {} + for i = 1, result.directory_names_count do + local dirname + pos, dirname = bin.unpack("p", packet.data, pos) + table.insert(result.directory_names, dirname) + end + + -- only one utf8 server name. note this string has a two-byte length. + _, result.utf8_server_name = bin.unpack(">P", packet.data, offsets.utf8_server_name + 1) + response.result = result + + return response + end, + + + --- Sends an FPGetUserInfo AFP request to the server and handles the response + -- + -- @return response object with the following result user_bitmap and + -- uid fields + fp_get_user_info = function( self ) + + local packet, pos, status, response + local data_offset = 0 + local flags = 1 -- Default User + local uid = 0 + local bitmap = USER_BITMAP.UserId + local result = {} + + local data = bin.pack( "CCI>S", COMMAND.FPGetUserInfo, flags, uid, bitmap ) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + + self:send_fp_packet( packet ) + response = self:read_fp_packet() + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + pos, response.result.user_bitmap, response.result.uid = bin.unpack(">S>I", packet.data) + + return response + end, + + --- Sends an FPGetSrvrParms AFP request to the server and handles the response + -- + -- @return response object with the following result server_time, + -- vol_count, volumes fields + fp_get_srvr_parms = function(self) + local packet, status, data + local data_offset = 0 + local response = {} + local pos = 0 + local parms = {} + + data = bin.pack("CC", COMMAND.FPGetSrvParms, 0) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet() + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + data = response:getPacketData() + pos, parms.server_time, parms.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 + + parms.volumes = {} + + for i=1, parms.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(parms.volumes, string.format("%s", volume_name) ) + end + + response:setResult(parms) + + return 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 currently supports the following authentication methods: + -- o No User Authent + -- o DHCAST128 + -- + -- The DHCAST128 UAM should work against most servers even though it's + -- superceeded by the DHX2 UAM. + -- + -- @param afp_version string (AFP3.3|AFP3.2|AFP3.1) + -- @param uam string containing authentication information + -- @return Response object + fp_login = function( self, afp_version, uam, username, password, options ) + local packet, status, data + local data_offset = 0 + local status, response + + if not HAVE_SSL then + response = Response:new() + response:setErrorMessage("OpenSSL not available, aborting ...") + return response + end + + -- 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 + response = Response:new() + response:setErrorMessage("Incorrect AFP version") + return response + end + + if ( uam == "No User Authent" ) then + data = bin.pack( "CCACA", COMMAND.FPLogin, afp_version:len(), afp_version, uam:len(), uam ) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet( ) + elseif( uam == "DHCAST128" ) then + local dhx_s2civ, dhx_c2civ = 'CJalbert', 'LWallace' + local p, g, Ra, Ma, Mb, K, nonce + local EncData, PlainText, K_bin, auth_response + local _, Id + local username = username or "" + local password = password or "" + + if ( bit.mod(username:len(), 2) == 0 ) then + username = username .. string.char(0) + end + + p = openssl.bignum_hex2bn("BA2873DFB06057D43F2024744CEEE75B") + g = openssl.bignum_dec2bn("7") + Ra = openssl.bignum_hex2bn("86F6D3C0B0D63E4B11F113A2F9F19E3BBBF803F28D30087A1450536BE979FD42") + Ma = openssl.bignum_mod_exp(g, Ra, p) + + data = bin.pack( "CpppA", COMMAND.FPLogin, afp_version, uam, username, openssl.bignum_bn2bin(Ma) ) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet( ) + if ( response:getErrorCode() ~= ERROR.FPAuthContinue ) then + return response + end + + if ( response.packet.header.length ~= 50 ) then + response:setErrorMessage("LoginContinue packet contained invalid data") + return response + end + + _, Id, Mb, EncData = bin.unpack(">SH16A32", response.packet.data ) + + Mb = openssl.bignum_hex2bn( Mb ) + K = openssl.bignum_mod_exp (Mb, Ra, p) + K_bin = openssl.bignum_bn2bin(K) + nonce = openssl.decrypt("cast5-cbc", K_bin, dhx_s2civ, EncData, false ):sub(1,16) + nonce = openssl.bignum_add( openssl.bignum_bin2bn(nonce), openssl.bignum_dec2bn("1") ) + PlainText = openssl.bignum_bn2bin(nonce) .. Util.ZeroPad(password, 64) + auth_response = openssl.encrypt( "cast5-cbc", K_bin, dhx_c2civ, PlainText, true) + + data = bin.pack( "CC>SA", COMMAND.FPLoginCont, 0, Id, auth_response ) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet( ) + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return response + end + return response + end + response:setErrorMessage("Unsupported uam: " .. uam or "nil") + return response + end, + + -- Terminates sessions and frees server resources established by FPLoginand FPLoginExt. + -- + -- @return response object + fp_logout = function( self ) + local packet, data, response + local data_offset, pad = 0, 0 + + data = bin.pack("CC", COMMAND.FPLogout, pad) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet( ) + end, + + --- Sends an FPOpenVol request to the server and handles the response + -- + -- @param bitmap number bitmask of volume information to request + -- @param volume_name string containing the volume name to query + -- @return response object with the following result bitmap and + -- volume_id fields + fp_open_vol = function( self, bitmap, volume_name ) + local packet, status, pos, data + local data_offset, pad = 0, 0 + local response, volume = {}, {} + + data = bin.pack("CC>SCA", COMMAND.FPOpenVol, pad, bitmap, volume_name:len(), volume_name ) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet() + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + pos, volume.bitmap, volume.volume_id = bin.unpack(">S>S", response.packet.data) + response:setResult(volume) + return response + end, + + + --- Sends an FPGetFileDirParms request to the server and handles the response + -- + -- @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 response object with the following result file_bitmap, dir_bitmap, + -- file_type and (dir or file tables) depending on whether + -- did is a file or directory + fp_get_file_dir_parms = function( self, volume_id, did, file_bitmap, dir_bitmap, path ) + + local packet, status, data + local data_offset = 0 + local pad = 0 + local response, parms = {}, {} + local pos + + if ( did == nil ) then + response = Response:new() + response:setErrorMessage("No Directory Id supplied") + return response + end + + if ( volume_id == nil ) then + response = Response:new() + response:setErrorMessage("No Volume Id supplied") + return response + end + + 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 = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet() + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + pos, parms.file_bitmap, parms.dir_bitmap, parms.file_type, pad = bin.unpack( ">S>SCC", response.packet.data ) + + -- file or dir? + if ( parms.file_type == 0x80 ) then + pos, parms.dir = Util.decode_dir_bitmap( parms.dir_bitmap, response.packet.data, pos ) + else + -- file + pos, parms.file = Util.decode_file_bitmap( parms.file_bitmap, response.packet.data, pos ) + end + + response:setResult(parms) + return response + end, + + --- Sends an FPEnumerateExt2 request to the server and handles the response + -- + -- @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 response object with the following result set to a table of tables containing + -- file_bitmap, dir_bitmap, req_count fields + fp_enumerate_ext2 = function( self, volume_id, did, file_bitmap, dir_bitmap, req_count, start_index, reply_size, path ) + + local packet, pos, _, status + local data_offset = 0 + local pad = 0 + local response,records = {}, {} + + 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>ICCA", req_count, start_index, reply_size, path.type, path.len, path.name ) + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + + self:send_fp_packet( packet ) + response = self:read_fp_packet( ) + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + pos, file_bitmap, dir_bitmap, req_count = bin.unpack(">S>S>S", response.packet.data) + + records = {} + + for i=1, req_count do + local record = {} + local len, _, ftype + + pos, len, ftype, _ = bin.unpack(">SCC", response.packet.data, pos) + + if ( ftype == 0x80 ) then + _, record = Util.decode_dir_bitmap( dir_bitmap, response.packet.data, pos ) + else + -- file + _, record = Util.decode_file_bitmap( file_bitmap, response.packet.data, pos ) + end + + if bit.mod( len, 2 ) ~= 0 then + len = len + 1 + end + + pos = pos + ( len - 4 ) + + record.type = ftype + table.insert(records, record) + end + + response:setResult(records) + return response + end, + + --- Sends an FPOpenFork request to the server and handles the response + -- + -- @param flag 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 response object with the following result contents file_bitmap and fork_id + fp_open_fork = function( self, flag, volume_id, did, file_bitmap, access_mode, path ) + + local packet, _ + local data_offset = 0 + local pad = 0 + local response, fork = {}, {} + + local data = bin.pack( "CC>S>I>S>S", COMMAND.FPOpenFork, flag, volume_id, did, file_bitmap, access_mode ) + + if path.type == PATH_TYPE.LongName then + data = data .. bin.pack( "CCA", path.type, path.len, path.name ) + end + + if path.type == PATH_TYPE.UTF8Name then + local unicode_hint = 0x08000103 + data = data .. bin.pack( "C>I>SA", path.type, unicode_hint, path.len, path.name ) + end + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet() + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + _, fork.file_bitmap, fork.fork_id = bin.unpack(">S>S", response.packet.data) + response:setResult(fork) + return response + end, + + --- FPCloseFork + -- + -- @param fork number containing the fork to close + -- @return response object + fp_close_fork = function( self, fork ) + local packet + local data_offset = 0 + local pad = 0 + local response = {} + + local data = bin.pack( "CC>S", COMMAND.FPCloseFork, pad, fork ) + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet( ) + end, + + --- FPCreateDir + -- + -- @param vol_id number containing the volume id + -- @param dir_id number containing the directory id + -- @param path string containing the name of the directory + -- @return response object + fp_create_dir = function( self, vol_id, dir_id, path ) + local packet + local data_offset, pad = 0, 0 + local response = {} + + local data = bin.pack( "CC>S>ICp", COMMAND.FPCreateDir, pad, vol_id, dir_id, path.type, path.name ) + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet( ) + end, + + --- Sends an FPCloseVol request to the server and handles the response + -- + -- @param volume_id number containing the id of the volume to close + -- @return response object + fp_close_vol = function( self, volume_id ) + local packet + local data_offset, pad = 0, 0 + local response = {} + + local data = bin.pack( "CC>S", COMMAND.FPCloseVol, pad, volume_id ) + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet( ) + end, + + --- FPReadExt + -- + -- @param fork number containing the open fork + -- @param offset number containing the offset from where writing should start. Negative value indicates offset from the end of the fork + -- @param count number containing the number of bytes to be written + -- @return response object + fp_read_ext = function( self, fork, offset, count ) + local pad = 0 + local packet, response + local data_offset = 0 + local block_size = 1024 + local data = bin.pack( "CC>S>L>L", COMMAND.FPReadExt, pad, fork, offset, count ) + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet( ) + + if ( response:getErrorCode() == ERROR.FPEOFErr and response.packet.header.length > 0 ) then + response:setErrorCode( ERROR.FPNoErr ) + end + + response:setResult( response.packet.data ) + return response + end, + + --- FPWriteExt + -- + -- @param flag number indicates whether Offset is relative to the beginning or end of the fork. + -- @param fork number containing the open fork + -- @param offset number containing the offset from where writing should start. Negative value indicates offset from the end of the fork + -- @param count number containing the number of bytes to be written + -- @param fdata string containing the data to be written + -- @return response object + fp_write_ext = function( self, flag, fork, offset, count, fdata ) + local packet + local data_offset = 20 + local data + + if count > fdata:len() then + local err = Response:new() + err:setErrorMessage("fp_write_ext: Count is greater than the amount of data") + return err + end + if count < 0 then + local err = Response:new() + err:setErrorMessage("fp_write_ext: Count must exceed zero") + return err + end + + data = bin.pack( "CC>S>L>LA", COMMAND.FPWriteExt, flag, fork, offset, count, fdata ) + packet = self:create_fp_packet( REQUEST.Write, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet( ) + end, + + --- FPCreateFile + -- + -- @param flag number where 0 indicates a soft create and 1 indicates a hard create. + -- @param vol_id number containing the volume id + -- @param did number containing the ancestor directory id + -- @param path string containing the path, including the volume, path and file name + -- @return response object + fp_create_file = function(self, flag, vol_id, did, path ) + local packet + local data_offset = 0 + local data = bin.pack( "CC>S>ICCA" , COMMAND.FPCreateFile, flag, vol_id, did, path.type, path.len, path.name ) + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + return self:read_fp_packet() + end, + + --- FPMapId + -- + -- @param subfunc number containing the subfunction to call + -- @param id number containing th id to translate + -- @return response object with the id in the result field + fp_map_id = function( self, subfunc, id ) + local packet, response + local data_offset = 0 + local data = bin.pack( "CC", COMMAND.FPMapId, subfunc ) + local _, len + + if ( subfunc == MAP_ID.UserUUIDToUTF8Name or subfunc == MAP_ID.GroupUUIDToUTF8Name ) then + data = data .. bin.pack(">L", id) + else + data = data .. bin.pack(">I", id) + end + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet( ) + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + -- Netatalk returns the name with 1-byte length prefix, + -- Mac OS has a 2-byte (UTF-8) length prefix + local _, len = bin.unpack("C", response.packet.data) + + -- if length is zero assume 2-byte length (UTF-8 name) + if len == 0 then + response:setResult( select(2, bin.unpack(">P", response.packet.data )) ) + else + response:setResult( select(2, bin.unpack("p", response.packet.data )) ) + end + return response + end, + + --- FPMapName + -- + -- @param subfunc number containing the subfunction to call + -- @param name string containing name to map + -- @return response object with the mapped name in the result field + fp_map_name = function( self, subfunc, name ) + local packet + local data_offset = 0 + local data = bin.pack( "CC>SA", COMMAND.FPMapName, subfunc, name:len(), name ) + local response + + packet = self:create_fp_packet( REQUEST.Command, data_offset, data ) + self:send_fp_packet( packet ) + response = self:read_fp_packet( ) + + if response:getErrorCode() ~= ERROR.FPNoErr then + return response + end + + response:setResult( select(2, bin.unpack(">I", response.packet.data))) + return response + end, } --- The helper class wraps the protocol class and their functions. It contains @@ -1338,779 +1338,779 @@ Proto = { -- minimizing the need to fully understand the AFP low-level protocol details. Helper = { - --- Creates a new helper object - new = function(self,o) - local o = {} - setmetatable(o, self) - self.__index = self - o.username = stdnse.get_script_args("afp.username") - o.password = stdnse.get_script_args("afp.password") - return o - end, - - --- Connects to the remote server and establishes a new AFP session - -- - -- @param host table as recieved by the action function of the script - -- @param port table as recieved by the action function of the script - -- @return status boolean - -- @return string containing error message (if status is false) - OpenSession = function( self, host, port ) - local status, response - - self.socket = nmap.new_socket() - self.socket:set_timeout( 5000 ) - status = self.socket:connect(host, port) - if not status then - return false, "Socket connection failed" - end - - self.proto = Proto:new( { socket=self.socket} ) - response = self.proto:dsi_open_session(self.socket) - - if response:getErrorCode() ~= ERROR.FPNoErr then - self.socket:close() - return false, response:getErrorMessage() - end - - return true - end, - - --- Closes the AFP session and then the socket - -- - -- @return status boolean - -- @return string containing error message (if status is false) - CloseSession = function( self ) - local status, packet = self.proto:dsi_close_session( ) - self.socket:close() - - return status, packet - end, - - --- Terminates the connection, withou closing the AFP session - -- - -- @return status (always true) - -- @return string (always "") - Terminate = function( self ) - self.socket:close() - return true,"" - end, - - --- Logs in to an AFP service - -- - -- @param username (optional) string containing the username - -- @param password (optional) string containing the user password - -- @param options table containing additional options uam - Login = function( self, username, password, options ) - local uam = ( options and options.UAM ) and options.UAM or "DHCAST128" - local response - - -- username and password arguments override the ones supplied using the - -- script arguments afp.username and afp.password - local username = username or self.username - local password = password or self.password - - if ( username and uam == "DHCAST128" ) then - response = self.proto:fp_login( "AFP3.1", "DHCAST128", username, password ) - elseif( username ) then - return false, ("Unsupported UAM: %s"):format(uam) - else - response = self.proto:fp_login( "AFP3.1", "No User Authent" ) - end - - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - return true, "Success" - end, - - --- Logs out from the AFP service - Logout = function(self) - return self.proto:fp_logout() - end, - - --- Walks the directory tree specified by str_path and returns the node information - -- - -- @param str_path string containing the directory - -- @return status boolean true on success, otherwise false - -- @return item table containing node information DirectoryId and DirectoryName - WalkDirTree = function( self, str_path ) - local status, response, path - local elements = stdnse.strsplit( "/", str_path ) - local f_bm = FILE_BITMAP.NodeId + FILE_BITMAP.ParentDirId + FILE_BITMAP.LongName - local d_bm = DIR_BITMAP.NodeId + DIR_BITMAP.ParentDirId + DIR_BITMAP.LongName - local item = { DirectoryId = 2 } - - response = self.proto:fp_open_vol( VOL_BITMAP.ID, elements[1] ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - item.VolumeId = response.result.volume_id - item.DirectoryName = str_path - - for i=2, #elements do - path = { ['type']=PATH_TYPE.LongName, name=elements[i], len=elements[i]:len() } - response = self.proto:fp_get_file_dir_parms( item.VolumeId, item.DirectoryId, f_bm, d_bm, path ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - item.DirectoryId = response.result.dir.NodeId - item.DirectoryName = response.result.dir.LongName - end - - return true, item - end, - - --- Reads a file on the AFP server - -- - -- @param str_path string containing the AFP sharepoint, path and filename eg. HR/Documents/File.doc - -- @return status boolean true on success, false on failure - -- @return content string containing the file contents - ReadFile = function( self, str_path ) - local status, response, fork, content, vol_name - local offset, count, did = 0, 1024, 2 - local status, path, vol_id - local p = Util.SplitPath( str_path ) - - status, response = self:WalkDirTree( p.dir ) - if ( not status ) then - return false, response - end - - vol_id = response.VolumeId - did = response.DirectoryId - - path = { ['type']=PATH_TYPE.LongName, name=p.file, len=p.file:len() } - - response = self.proto:fp_open_fork(0, vol_id, did, 0, ACCESS_MODE.Read, path ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - fork = response.result.fork_id - content = "" - - while true do - response = self.proto:fp_read_ext( fork, offset, count ) - if response:getErrorCode() ~= ERROR.FPNoErr then - break - end - content = content .. response.result - offset = offset + count - end - - response = self.proto:fp_close_fork( fork ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - return true, content - end, - - --- Writes a file to the AFP server - -- - -- @param str_path string containing the AFP sharepoint, path and filename eg. HR/Documents/File.doc - -- @param fdata string containing the data to write to the file - -- @return status boolean true on success, false on failure - -- @return error string containing error message if status is false - WriteFile = function( self, str_path, fdata ) - local status, response, fork, content - local offset, count = 1, 1024 - local status, vol_id, did, path - local p = Util.SplitPath( str_path ) - - status, response = self:WalkDirTree( p.dir ) - vol_id = response.VolumeId - did = response.DirectoryId - - if ( not status ) then - return false, response - end - - path = { ['type']=PATH_TYPE.LongName, name=p.file, len=p.file:len() } - - status, response = self.proto:fp_create_file( 0, vol_id, did, path ) - if not status then - if ( response.header.error_code ~= ERROR.FPObjectExists ) then - return false, response.header.error_msg - end - end - - response = self.proto:fp_open_fork( 0, vol_id, did, 0, ACCESS_MODE.Write, path ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - fork = response.result.fork_id - - response = self.proto:fp_write_ext( 0, fork, 0, fdata:len(), fdata ) - - return true, nil - end, - - --- Maps a user id (uid) to a user name - -- - -- @param uid number containing the uid to resolve - -- @return status boolean true on success, false on failure - -- @return username string on success - -- error string on failure - UIDToName = function( self, uid ) - local response = self.proto:fp_map_id( MAP_ID.UserIDToName, uid ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - return true, response.result - end, - - --- Maps a group id (gid) to group name - -- - -- @param gid number containing the gid to lookup - -- @return status boolean true on success, false on failure - -- @return groupname string on success - -- error string on failure - GIDToName = function( self, gid ) - local response = self.proto:fp_map_id( MAP_ID.GroupIDToName, gid ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - return true, response.result - end, - - --- Maps a username to a UID - -- - -- @param name string containing the username to map to an UID - -- @return status boolean true on success, false on failure - -- @return UID number on success - -- error string on failure - NameToUID = function( self, name ) - local response = self.proto:fp_map_name( MAP_NAME.NameToUserID, name ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - return true, response.result - end, - - --- List the contents of a directory - -- - -- @param str_path string containing the sharepoint and directory names - -- @param options table options containing zero or more of the options - -- max_depth and dironly - -- @param depth number containing the current depth (used when called recursively) - -- @param parent table containing information about the parent object (used when called recursively) - -- @return status boolean true on success, false on failure - -- @return dir table containing a table for each directory item with the following type, - -- name and id - Dir = function( self, str_path, options, depth, parent ) - local status, result - local depth = depth or 1 - local options = options or { max_depth = 1 } - local response, records - local f_bm = FILE_BITMAP.NodeId + FILE_BITMAP.ParentDirId + FILE_BITMAP.LongName - local d_bm = DIR_BITMAP.NodeId + DIR_BITMAP.ParentDirId + DIR_BITMAP.LongName - local path = { ['type']=PATH_TYPE.LongName, name="", len=0 } - - local TYPE_DIR = 0x80 - - if ( parent == nil ) then - status, response = self:WalkDirTree( str_path ) - if ( not status ) then - return false, response - end - - parent = {} - parent.vol_id = response.VolumeId - parent.did = response.DirectoryId - parent.dir_name = response.DirectoryName or "" - parent.out_tbl = {} - end - - if ( options and options.max_depth and options.max_depth > 0 and options.max_depth < depth ) then - return false, "Max Depth Reached" - end - - response = self.proto:fp_enumerate_ext2( parent.vol_id, parent.did, f_bm, d_bm, 1000, 1, 52800, path) - - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - records = response.result or {} - local dir_item = {} - - for _, record in ipairs( records ) do - if ( options and options.dironly ) then - if ( record.type == TYPE_DIR ) then - table.insert( dir_item, { ['type'] = record.type, ['name'] = record.LongName, ['id'] = record.NodeId } ) - end - else - table.insert( dir_item, { ['type'] = record.type, ['name'] = record.LongName, ['id'] = record.NodeId } ) - end - if ( record.type == TYPE_DIR ) then - self:Dir("", options, depth + 1, { vol_id = parent.vol_id, did=record.NodeId, dir_name=record.LongName, out_tbl=dir_item} ) - end - end - - table.insert( parent.out_tbl, dir_item ) - - return true, parent.out_tbl - end, - - --- Displays a directory tree - -- - -- @param str_path string containing the sharepoint and the directory - -- @param options table options containing zero or more of the options - -- max_depth and dironly - -- @return dirtree table containing the directories - DirTree = function( self, str_path, options ) - local options = options or {} - options.dironly = true - return self:Dir( str_path, options ) - end, - - --- List the AFP sharepoints - -- - -- @return volumes table containing the sharepoints - ListShares = function( self ) - local response - response = self.proto:fp_get_srvr_parms( ) - - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - return true, response.result.volumes - end, - - --- Determine the sharepoint permissions - -- - -- @param vol_name string containing the name of the volume - -- @return status boolean true on success, false on failure - -- @return acls table containing the volume acls as returned by acls_to_long_string - GetSharePermissions = function( self, vol_name ) - local status, response, vol_id, acls - - response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) - - if response:getErrorCode() == ERROR.FPNoErr then - local vol_id - local path = {} - - vol_id = response.result.volume_id - path.type = PATH_TYPE.LongName - path.name = "" - path.len = path.name:len() - - response = self.proto:fp_get_file_dir_parms( vol_id, 2, FILE_BITMAP.ALL, DIR_BITMAP.ALL, path ) - if response:getErrorCode() == ERROR.FPNoErr then - if ( response.result.dir and response.result.dir.AccessRights ) then - acls = Util.acls_to_long_string(response.result.dir.AccessRights) - acls.name = nil - end - end - self.proto:fp_close_vol( vol_id ) - end - - return true, acls - end, - - --- Gets the Unix permissions of a file - -- @param vol_name string containing the name of the volume - -- @param str_path string containing the name of the file - -- @return status true on success, false on failure - -- @return acls table (on success) containing the following fields - -- uid - a numeric user identifier - -- gid - a numeric group identifier - -- privs - a string value representing the permissions - -- eg: drwx------ - -- @return err string (on failure) containing the error message - GetFileUnixPermissions = function(self, vol_name, str_path) - local response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) - - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return false, response:getErrorMessage() - end - - local vol_id = response.result.volume_id - local path = { type = PATH_TYPE.LongName, name = str_path, len = #str_path } - response = self.proto:fp_get_file_dir_parms( vol_id, 2, FILE_BITMAP.UnixPrivileges, DIR_BITMAP.UnixPrivileges, path ) - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return false, response:getErrorMessage() - end - - local item = ( response.result.file ) and response.result.file or response.result.dir - local item_type = ( response.result.file ) and "-" or "d" - local privs = ( item.UnixPrivileges and item.UnixPrivileges.ua_permissions ) and - item.UnixPrivileges.ua_permissions - if ( privs ) then - local uid = item.UnixPrivileges.uid - local gid = item.UnixPrivileges.gid - local str_privs = item_type .. Util.decode_unix_privs(privs) - return true, { uid = uid, gid = gid, privs = str_privs } - end - end, - - --- Gets the Unix permissions of a file - -- @param vol_name string containing the name of the volume - -- @param str_path string containing the name of the file - -- @return status true on success, false on failure - -- @return size containing the size of the file in bytes - -- @return err string (on failure) containing the error message - GetFileSize = function( self, vol_name, str_path ) - local response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) - - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return false, response:getErrorMessage() - end - - local vol_id = response.result.volume_id - local path = { type = PATH_TYPE.LongName, name = str_path, len = #str_path } - response = self.proto:fp_get_file_dir_parms( vol_id, 2, FILE_BITMAP.ExtendedDataForkSize, 0, path ) - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return false, response:getErrorMessage() - end - - return true, ( response.result.file and - response.result.file.ExtendedDataForkSize) and - response.result.file.ExtendedDataForkSize or 0 - end, - - - --- Returns the creation, modification and backup dates of a file - -- @param vol_name string containing the name of the volume - -- @param str_path string containing the name of the file - -- @return status true on success, false on failure - -- @return dates table containing the following fields: - -- create - Creation date of the file - -- modify - Modification date of the file - -- backup - Date of last backup - -- @return err string (on failure) containing the error message - GetFileDates = function( self, vol_name, str_path ) - local response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) - - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return false, response:getErrorMessage() - end - - local vol_id = response.result.volume_id - local path = { type = PATH_TYPE.LongName, name = str_path, len = #str_path } - local f_bm = FILE_BITMAP.CreationDate + FILE_BITMAP.ModificationDate + FILE_BITMAP.BackupDate - local d_bm = DIR_BITMAP.CreationDate + DIR_BITMAP.ModificationDate + DIR_BITMAP.BackupDate - response = self.proto:fp_get_file_dir_parms( vol_id, 2, f_bm, d_bm, path ) - if ( response:getErrorCode() ~= ERROR.FPNoErr ) then - return false, response:getErrorMessage() - end - - local item = ( response.result.file ) and response.result.file or response.result.dir - - local diff = os.time{year=2000, month=1, day=1, hour=0} - os.time{year=1970, month=1, day=1, hour=0} - local create = os.date("%Y-%m-%d %H:%M", item.CreationDate + diff) - local backup = os.date("%Y-%m-%d %H:%M", item.BackupDate ) - local modify = os.date("%Y-%m-%d %H:%M", item.ModificationDate + diff ) - - return true, { create = create, backup = backup, modify = modify } - end, - - --- Creates a new directory on the AFP sharepoint - -- - -- @param str_path containing the sharepoint and the directory - -- @return status boolean true on success, false on failure - -- @return dirId number containing the new directory id - CreateDir = function( self, str_path ) - local status, response, vol_id, did - local p = Util.SplitPath( str_path ) - local path = { ['type']=PATH_TYPE.LongName, name=p.file, len=p.file:len() } - - - status, response = self:WalkDirTree( p.dir ) - if not status then - return false, response - end - - response = self.proto:fp_create_dir( response.VolumeId, response.DirectoryId, path ) - if response:getErrorCode() ~= ERROR.FPNoErr then - return false, response:getErrorMessage() - end - - return true, response - end, + --- Creates a new helper object + new = function(self,o) + local o = {} + setmetatable(o, self) + self.__index = self + o.username = stdnse.get_script_args("afp.username") + o.password = stdnse.get_script_args("afp.password") + return o + end, + + --- Connects to the remote server and establishes a new AFP session + -- + -- @param host table as recieved by the action function of the script + -- @param port table as recieved by the action function of the script + -- @return status boolean + -- @return string containing error message (if status is false) + OpenSession = function( self, host, port ) + local status, response + + self.socket = nmap.new_socket() + self.socket:set_timeout( 5000 ) + status = self.socket:connect(host, port) + if not status then + return false, "Socket connection failed" + end + + self.proto = Proto:new( { socket=self.socket} ) + response = self.proto:dsi_open_session(self.socket) + + if response:getErrorCode() ~= ERROR.FPNoErr then + self.socket:close() + return false, response:getErrorMessage() + end + + return true + end, + + --- Closes the AFP session and then the socket + -- + -- @return status boolean + -- @return string containing error message (if status is false) + CloseSession = function( self ) + local status, packet = self.proto:dsi_close_session( ) + self.socket:close() + + return status, packet + end, + + --- Terminates the connection, withou closing the AFP session + -- + -- @return status (always true) + -- @return string (always "") + Terminate = function( self ) + self.socket:close() + return true,"" + end, + + --- Logs in to an AFP service + -- + -- @param username (optional) string containing the username + -- @param password (optional) string containing the user password + -- @param options table containing additional options uam + Login = function( self, username, password, options ) + local uam = ( options and options.UAM ) and options.UAM or "DHCAST128" + local response + + -- username and password arguments override the ones supplied using the + -- script arguments afp.username and afp.password + local username = username or self.username + local password = password or self.password + + if ( username and uam == "DHCAST128" ) then + response = self.proto:fp_login( "AFP3.1", "DHCAST128", username, password ) + elseif( username ) then + return false, ("Unsupported UAM: %s"):format(uam) + else + response = self.proto:fp_login( "AFP3.1", "No User Authent" ) + end + + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + return true, "Success" + end, + + --- Logs out from the AFP service + Logout = function(self) + return self.proto:fp_logout() + end, + + --- Walks the directory tree specified by str_path and returns the node information + -- + -- @param str_path string containing the directory + -- @return status boolean true on success, otherwise false + -- @return item table containing node information DirectoryId and DirectoryName + WalkDirTree = function( self, str_path ) + local status, response, path + local elements = stdnse.strsplit( "/", str_path ) + local f_bm = FILE_BITMAP.NodeId + FILE_BITMAP.ParentDirId + FILE_BITMAP.LongName + local d_bm = DIR_BITMAP.NodeId + DIR_BITMAP.ParentDirId + DIR_BITMAP.LongName + local item = { DirectoryId = 2 } + + response = self.proto:fp_open_vol( VOL_BITMAP.ID, elements[1] ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + item.VolumeId = response.result.volume_id + item.DirectoryName = str_path + + for i=2, #elements do + path = { ['type']=PATH_TYPE.LongName, name=elements[i], len=elements[i]:len() } + response = self.proto:fp_get_file_dir_parms( item.VolumeId, item.DirectoryId, f_bm, d_bm, path ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + item.DirectoryId = response.result.dir.NodeId + item.DirectoryName = response.result.dir.LongName + end + + return true, item + end, + + --- Reads a file on the AFP server + -- + -- @param str_path string containing the AFP sharepoint, path and filename eg. HR/Documents/File.doc + -- @return status boolean true on success, false on failure + -- @return content string containing the file contents + ReadFile = function( self, str_path ) + local status, response, fork, content, vol_name + local offset, count, did = 0, 1024, 2 + local status, path, vol_id + local p = Util.SplitPath( str_path ) + + status, response = self:WalkDirTree( p.dir ) + if ( not status ) then + return false, response + end + + vol_id = response.VolumeId + did = response.DirectoryId + + path = { ['type']=PATH_TYPE.LongName, name=p.file, len=p.file:len() } + + response = self.proto:fp_open_fork(0, vol_id, did, 0, ACCESS_MODE.Read, path ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + fork = response.result.fork_id + content = "" + + while true do + response = self.proto:fp_read_ext( fork, offset, count ) + if response:getErrorCode() ~= ERROR.FPNoErr then + break + end + content = content .. response.result + offset = offset + count + end + + response = self.proto:fp_close_fork( fork ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + return true, content + end, + + --- Writes a file to the AFP server + -- + -- @param str_path string containing the AFP sharepoint, path and filename eg. HR/Documents/File.doc + -- @param fdata string containing the data to write to the file + -- @return status boolean true on success, false on failure + -- @return error string containing error message if status is false + WriteFile = function( self, str_path, fdata ) + local status, response, fork, content + local offset, count = 1, 1024 + local status, vol_id, did, path + local p = Util.SplitPath( str_path ) + + status, response = self:WalkDirTree( p.dir ) + vol_id = response.VolumeId + did = response.DirectoryId + + if ( not status ) then + return false, response + end + + path = { ['type']=PATH_TYPE.LongName, name=p.file, len=p.file:len() } + + status, response = self.proto:fp_create_file( 0, vol_id, did, path ) + if not status then + if ( response.header.error_code ~= ERROR.FPObjectExists ) then + return false, response.header.error_msg + end + end + + response = self.proto:fp_open_fork( 0, vol_id, did, 0, ACCESS_MODE.Write, path ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + fork = response.result.fork_id + + response = self.proto:fp_write_ext( 0, fork, 0, fdata:len(), fdata ) + + return true, nil + end, + + --- Maps a user id (uid) to a user name + -- + -- @param uid number containing the uid to resolve + -- @return status boolean true on success, false on failure + -- @return username string on success + -- error string on failure + UIDToName = function( self, uid ) + local response = self.proto:fp_map_id( MAP_ID.UserIDToName, uid ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + return true, response.result + end, + + --- Maps a group id (gid) to group name + -- + -- @param gid number containing the gid to lookup + -- @return status boolean true on success, false on failure + -- @return groupname string on success + -- error string on failure + GIDToName = function( self, gid ) + local response = self.proto:fp_map_id( MAP_ID.GroupIDToName, gid ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + return true, response.result + end, + + --- Maps a username to a UID + -- + -- @param name string containing the username to map to an UID + -- @return status boolean true on success, false on failure + -- @return UID number on success + -- error string on failure + NameToUID = function( self, name ) + local response = self.proto:fp_map_name( MAP_NAME.NameToUserID, name ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + return true, response.result + end, + + --- List the contents of a directory + -- + -- @param str_path string containing the sharepoint and directory names + -- @param options table options containing zero or more of the options + -- max_depth and dironly + -- @param depth number containing the current depth (used when called recursively) + -- @param parent table containing information about the parent object (used when called recursively) + -- @return status boolean true on success, false on failure + -- @return dir table containing a table for each directory item with the following type, + -- name and id + Dir = function( self, str_path, options, depth, parent ) + local status, result + local depth = depth or 1 + local options = options or { max_depth = 1 } + local response, records + local f_bm = FILE_BITMAP.NodeId + FILE_BITMAP.ParentDirId + FILE_BITMAP.LongName + local d_bm = DIR_BITMAP.NodeId + DIR_BITMAP.ParentDirId + DIR_BITMAP.LongName + local path = { ['type']=PATH_TYPE.LongName, name="", len=0 } + + local TYPE_DIR = 0x80 + + if ( parent == nil ) then + status, response = self:WalkDirTree( str_path ) + if ( not status ) then + return false, response + end + + parent = {} + parent.vol_id = response.VolumeId + parent.did = response.DirectoryId + parent.dir_name = response.DirectoryName or "" + parent.out_tbl = {} + end + + if ( options and options.max_depth and options.max_depth > 0 and options.max_depth < depth ) then + return false, "Max Depth Reached" + end + + response = self.proto:fp_enumerate_ext2( parent.vol_id, parent.did, f_bm, d_bm, 1000, 1, 52800, path) + + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + records = response.result or {} + local dir_item = {} + + for _, record in ipairs( records ) do + if ( options and options.dironly ) then + if ( record.type == TYPE_DIR ) then + table.insert( dir_item, { ['type'] = record.type, ['name'] = record.LongName, ['id'] = record.NodeId } ) + end + else + table.insert( dir_item, { ['type'] = record.type, ['name'] = record.LongName, ['id'] = record.NodeId } ) + end + if ( record.type == TYPE_DIR ) then + self:Dir("", options, depth + 1, { vol_id = parent.vol_id, did=record.NodeId, dir_name=record.LongName, out_tbl=dir_item} ) + end + end + + table.insert( parent.out_tbl, dir_item ) + + return true, parent.out_tbl + end, + + --- Displays a directory tree + -- + -- @param str_path string containing the sharepoint and the directory + -- @param options table options containing zero or more of the options + -- max_depth and dironly + -- @return dirtree table containing the directories + DirTree = function( self, str_path, options ) + local options = options or {} + options.dironly = true + return self:Dir( str_path, options ) + end, + + --- List the AFP sharepoints + -- + -- @return volumes table containing the sharepoints + ListShares = function( self ) + local response + response = self.proto:fp_get_srvr_parms( ) + + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + return true, response.result.volumes + end, + + --- Determine the sharepoint permissions + -- + -- @param vol_name string containing the name of the volume + -- @return status boolean true on success, false on failure + -- @return acls table containing the volume acls as returned by acls_to_long_string + GetSharePermissions = function( self, vol_name ) + local status, response, vol_id, acls + + response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) + + if response:getErrorCode() == ERROR.FPNoErr then + local vol_id + local path = {} + + vol_id = response.result.volume_id + path.type = PATH_TYPE.LongName + path.name = "" + path.len = path.name:len() + + response = self.proto:fp_get_file_dir_parms( vol_id, 2, FILE_BITMAP.ALL, DIR_BITMAP.ALL, path ) + if response:getErrorCode() == ERROR.FPNoErr then + if ( response.result.dir and response.result.dir.AccessRights ) then + acls = Util.acls_to_long_string(response.result.dir.AccessRights) + acls.name = nil + end + end + self.proto:fp_close_vol( vol_id ) + end + + return true, acls + end, + + --- Gets the Unix permissions of a file + -- @param vol_name string containing the name of the volume + -- @param str_path string containing the name of the file + -- @return status true on success, false on failure + -- @return acls table (on success) containing the following fields + -- uid - a numeric user identifier + -- gid - a numeric group identifier + -- privs - a string value representing the permissions + -- eg: drwx------ + -- @return err string (on failure) containing the error message + GetFileUnixPermissions = function(self, vol_name, str_path) + local response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) + + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return false, response:getErrorMessage() + end + + local vol_id = response.result.volume_id + local path = { type = PATH_TYPE.LongName, name = str_path, len = #str_path } + response = self.proto:fp_get_file_dir_parms( vol_id, 2, FILE_BITMAP.UnixPrivileges, DIR_BITMAP.UnixPrivileges, path ) + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return false, response:getErrorMessage() + end + + local item = ( response.result.file ) and response.result.file or response.result.dir + local item_type = ( response.result.file ) and "-" or "d" + local privs = ( item.UnixPrivileges and item.UnixPrivileges.ua_permissions ) and + item.UnixPrivileges.ua_permissions + if ( privs ) then + local uid = item.UnixPrivileges.uid + local gid = item.UnixPrivileges.gid + local str_privs = item_type .. Util.decode_unix_privs(privs) + return true, { uid = uid, gid = gid, privs = str_privs } + end + end, + + --- Gets the Unix permissions of a file + -- @param vol_name string containing the name of the volume + -- @param str_path string containing the name of the file + -- @return status true on success, false on failure + -- @return size containing the size of the file in bytes + -- @return err string (on failure) containing the error message + GetFileSize = function( self, vol_name, str_path ) + local response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) + + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return false, response:getErrorMessage() + end + + local vol_id = response.result.volume_id + local path = { type = PATH_TYPE.LongName, name = str_path, len = #str_path } + response = self.proto:fp_get_file_dir_parms( vol_id, 2, FILE_BITMAP.ExtendedDataForkSize, 0, path ) + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return false, response:getErrorMessage() + end + + return true, ( response.result.file and + response.result.file.ExtendedDataForkSize) and + response.result.file.ExtendedDataForkSize or 0 + end, + + + --- Returns the creation, modification and backup dates of a file + -- @param vol_name string containing the name of the volume + -- @param str_path string containing the name of the file + -- @return status true on success, false on failure + -- @return dates table containing the following fields: + -- create - Creation date of the file + -- modify - Modification date of the file + -- backup - Date of last backup + -- @return err string (on failure) containing the error message + GetFileDates = function( self, vol_name, str_path ) + local response = self.proto:fp_open_vol( VOL_BITMAP.ID, vol_name ) + + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return false, response:getErrorMessage() + end + + local vol_id = response.result.volume_id + local path = { type = PATH_TYPE.LongName, name = str_path, len = #str_path } + local f_bm = FILE_BITMAP.CreationDate + FILE_BITMAP.ModificationDate + FILE_BITMAP.BackupDate + local d_bm = DIR_BITMAP.CreationDate + DIR_BITMAP.ModificationDate + DIR_BITMAP.BackupDate + response = self.proto:fp_get_file_dir_parms( vol_id, 2, f_bm, d_bm, path ) + if ( response:getErrorCode() ~= ERROR.FPNoErr ) then + return false, response:getErrorMessage() + end + + local item = ( response.result.file ) and response.result.file or response.result.dir + + local diff = os.time{year=2000, month=1, day=1, hour=0} - os.time{year=1970, month=1, day=1, hour=0} + local create = os.date("%Y-%m-%d %H:%M", item.CreationDate + diff) + local backup = os.date("%Y-%m-%d %H:%M", item.BackupDate ) + local modify = os.date("%Y-%m-%d %H:%M", item.ModificationDate + diff ) + + return true, { create = create, backup = backup, modify = modify } + end, + + --- Creates a new directory on the AFP sharepoint + -- + -- @param str_path containing the sharepoint and the directory + -- @return status boolean true on success, false on failure + -- @return dirId number containing the new directory id + CreateDir = function( self, str_path ) + local status, response, vol_id, did + local p = Util.SplitPath( str_path ) + local path = { ['type']=PATH_TYPE.LongName, name=p.file, len=p.file:len() } + + + status, response = self:WalkDirTree( p.dir ) + if not status then + return false, response + end + + response = self.proto:fp_create_dir( response.VolumeId, response.DirectoryId, path ) + if response:getErrorCode() ~= ERROR.FPNoErr then + return false, response:getErrorMessage() + end + + return true, response + end, } --- Util class, containing some static functions used by Helper and Proto Util = { - --- Pads a string with zeroes - -- - -- @param str string containing the string to be padded - -- @param len number containing the length of the new string - -- @return str string containing the new string - ZeroPad = function( str, len ) - if len < str:len() then - return - end + --- Pads a string with zeroes + -- + -- @param str string containing the string to be padded + -- @param len number containing the length of the new string + -- @return str string containing the new string + ZeroPad = function( str, len ) + if len < str:len() then + return + end - for i=1, len - str:len() do - str = str .. string.char(0) - end + for i=1, len - str:len() do + str = str .. string.char(0) + end - return str - end, + return str + end, - --- Splits a path into two pieces, directory and file - -- - -- @param str_path string containing the path to split - -- @return dir table containing dir and file - SplitPath = function( str_path ) - local elements = stdnse.strsplit("/", str_path) - local dir, file = "", "" + --- Splits a path into two pieces, directory and file + -- + -- @param str_path string containing the path to split + -- @return dir table containing dir and file + SplitPath = function( str_path ) + local elements = stdnse.strsplit("/", str_path) + local dir, file = "", "" - if #elements < 2 then - return nil - end + if #elements < 2 then + return nil + end - file = elements[#elements] + file = elements[#elements] - table.remove( elements, #elements ) - dir = stdnse.strjoin( "/", elements ) + table.remove( elements, #elements ) + dir = stdnse.strjoin( "/", elements ) - return { ['dir']=dir, ['file']=file } + return { ['dir']=dir, ['file']=file } - end, + end, - --- Converts a group bitmask of Search, Read and Write to table - -- - -- @param acls number containing bitmasked acls - -- @return table of ACLs - acl_group_to_long_string = function(acls) + --- Converts a group bitmask of Search, Read and Write to table + -- + -- @param acls number containing bitmasked acls + -- @return table of ACLs + acl_group_to_long_string = function(acls) - local acl_table = {} + local acl_table = {} - if bit.band( acls, ACLS.OwnerSearch ) == ACLS.OwnerSearch then - table.insert( acl_table, "Search") - end + if bit.band( acls, ACLS.OwnerSearch ) == ACLS.OwnerSearch then + table.insert( acl_table, "Search") + end - if bit.band( acls, ACLS.OwnerRead ) == ACLS.OwnerRead then - table.insert( acl_table, "Read") - end + if bit.band( acls, ACLS.OwnerRead ) == ACLS.OwnerRead then + table.insert( acl_table, "Read") + end - if bit.band( acls, ACLS.OwnerWrite ) == ACLS.OwnerWrite then - table.insert( acl_table, "Write") - end + if bit.band( acls, ACLS.OwnerWrite ) == ACLS.OwnerWrite then + table.insert( acl_table, "Write") + end - return acl_table - 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 table of long ACLs - acls_to_long_string = function( acls ) + --- Converts a numeric acl to string + -- + -- @param acls number containig acls as recieved from fp_get_file_dir_parms + -- @return table of long ACLs + acls_to_long_string = function( acls ) - local owner = Util.acl_group_to_long_string( bit.band( acls, 255 ) ) - local group = Util.acl_group_to_long_string( bit.band( bit.rshift(acls, 8), 255 ) ) - local everyone = Util.acl_group_to_long_string( bit.band( bit.rshift(acls, 16), 255 ) ) - local user = Util.acl_group_to_long_string( bit.band( bit.rshift(acls, 24), 255 ) ) + local owner = Util.acl_group_to_long_string( bit.band( acls, 255 ) ) + local group = Util.acl_group_to_long_string( bit.band( bit.rshift(acls, 8), 255 ) ) + local everyone = Util.acl_group_to_long_string( bit.band( bit.rshift(acls, 16), 255 ) ) + local user = Util.acl_group_to_long_string( bit.band( bit.rshift(acls, 24), 255 ) ) - local blank = bit.band( acls, ACLS.BlankAccess ) == ACLS.BlankAccess and "Blank" or nil - local isowner = bit.band( acls, ACLS.UserIsOwner ) == ACLS.UserIsOwner and "IsOwner" or nil + local blank = bit.band( acls, ACLS.BlankAccess ) == ACLS.BlankAccess and "Blank" or nil + local isowner = bit.band( acls, ACLS.UserIsOwner ) == ACLS.UserIsOwner and "IsOwner" or nil - local options = {} + local options = {} - if blank then - table.insert(options, "Blank") - end + if blank then + table.insert(options, "Blank") + end - if isowner then - table.insert(options, "IsOwner") - end + if isowner then + table.insert(options, "IsOwner") + end - local acls_tbl = {} + 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) ) ) + 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 + if #options > 0 then + table.insert( acls_tbl, string.format( "Options: %s", stdnse.strjoin(",", options ) ) ) + end - return acls_tbl + return acls_tbl - end, + end, - --- Decodes the UnixPrivileges.ua_permissions value - -- - -- @param privs number containing the UnixPrivileges.ua_permissions value - -- @return string containing the ACL characters - decode_unix_privs = function( privs ) - local owner = ( bit.band( privs, ACLS.OwnerRead ) == ACLS.OwnerRead ) and "r" or "-" - owner = owner .. (( bit.band( privs, ACLS.OwnerWrite ) == ACLS.OwnerWrite ) and "w" or "-") - owner = owner .. (( bit.band( privs, ACLS.OwnerSearch ) == ACLS.OwnerSearch ) and "x" or "-") + --- Decodes the UnixPrivileges.ua_permissions value + -- + -- @param privs number containing the UnixPrivileges.ua_permissions value + -- @return string containing the ACL characters + decode_unix_privs = function( privs ) + local owner = ( bit.band( privs, ACLS.OwnerRead ) == ACLS.OwnerRead ) and "r" or "-" + owner = owner .. (( bit.band( privs, ACLS.OwnerWrite ) == ACLS.OwnerWrite ) and "w" or "-") + owner = owner .. (( bit.band( privs, ACLS.OwnerSearch ) == ACLS.OwnerSearch ) and "x" or "-") - local group = ( bit.band( privs, ACLS.GroupRead ) == ACLS.GroupRead ) and "r" or "-" - group = group .. (( bit.band( privs, ACLS.GroupWrite ) == ACLS.GroupWrite ) and "w" or "-") - group = group .. (( bit.band( privs, ACLS.GroupSearch ) == ACLS.GroupSearch ) and "x" or "-") + local group = ( bit.band( privs, ACLS.GroupRead ) == ACLS.GroupRead ) and "r" or "-" + group = group .. (( bit.band( privs, ACLS.GroupWrite ) == ACLS.GroupWrite ) and "w" or "-") + group = group .. (( bit.band( privs, ACLS.GroupSearch ) == ACLS.GroupSearch ) and "x" or "-") - local other = ( bit.band( privs, ACLS.EveryoneRead ) == ACLS.EveryoneRead ) and "r" or "-" - other = other .. (( bit.band( privs, ACLS.EveryoneWrite ) == ACLS.EveryoneWrite ) and "w" or "-") - other = other .. (( bit.band( privs, ACLS.EveryoneSearch ) == ACLS.EveryoneSearch ) and "x" or "-") + local other = ( bit.band( privs, ACLS.EveryoneRead ) == ACLS.EveryoneRead ) and "r" or "-" + other = other .. (( bit.band( privs, ACLS.EveryoneWrite ) == ACLS.EveryoneWrite ) and "w" or "-") + other = other .. (( bit.band( privs, ACLS.EveryoneSearch ) == ACLS.EveryoneSearch ) and "x" or "-") - return owner .. group .. other - end, + return owner .. group .. other + end, - --- Decodes a file bitmap - -- - -- @param bitmap number containing the bitmap - -- @param data string containing the data to be decoded - -- @param pos number containing the offset into data - -- @return pos number containing the new offset after decoding - -- @return file table containing the decoded values - decode_file_bitmap = function( bitmap, data, pos ) - local file = {} + --- Decodes a file bitmap + -- + -- @param bitmap number containing the bitmap + -- @param data string containing the data to be decoded + -- @param pos number containing the offset into data + -- @return pos number containing the new offset after decoding + -- @return file table containing the decoded values + decode_file_bitmap = function( bitmap, data, pos ) + local file = {} - if ( bit.band( bitmap, FILE_BITMAP.Attributes ) == FILE_BITMAP.Attributes ) then - pos, file.Attributes = bin.unpack(">S", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.ParentDirId ) == FILE_BITMAP.ParentDirId ) then - pos, file.ParentDirId = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.CreationDate ) == FILE_BITMAP.CreationDate ) then - pos, file.CreationDate = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.ModificationDate ) == FILE_BITMAP.ModificationDate ) then - pos, file.ModificationDate = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.BackupDate ) == FILE_BITMAP.BackupDate ) then - pos, file.BackupDate = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.FinderInfo ) == FILE_BITMAP.FinderInfo ) then - pos, file.FinderInfo = bin.unpack("A32", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.LongName ) == FILE_BITMAP.LongName ) then - local offset, p, name - pos, offset = bin.unpack(">S", data, pos) - p, file.LongName = bin.unpack("p", data, offset + pos - 1) - end - if ( bit.band( bitmap, FILE_BITMAP.ShortName ) == FILE_BITMAP.ShortName ) then - local offset, p, name - pos, offset = bin.unpack(">S", data, pos) - p, file.ShortName = bin.unpack("p", data, offset + pos - 1) - end - if ( bit.band( bitmap, FILE_BITMAP.NodeId ) == FILE_BITMAP.NodeId ) then - pos, file.NodeId = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.DataForkSize ) == FILE_BITMAP.DataForkSize ) then - pos, file.DataForkSize = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.ResourceForkSize ) == FILE_BITMAP.ResourceForkSize ) then - pos, file.ResourceForkSize = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.ExtendedDataForkSize ) == FILE_BITMAP.ExtendedDataForkSize ) then - pos, file.ExtendedDataForkSize = bin.unpack(">L", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.LaunchLimit ) == FILE_BITMAP.LaunchLimit ) then - -- should not be set as it's deprecated according to: - -- http://developer.apple.com/mac/library/documentation/Networking/Reference/AFP_Reference/Reference/reference.html#//apple_ref/doc/c_ref/kFPLaunchLimitBit - end - if ( bit.band( bitmap, FILE_BITMAP.UTF8Name ) == FILE_BITMAP.UTF8Name ) then - local offset, p, name - pos, offset = bin.unpack(">S", data, pos) - p, file.UTF8Name = bin.unpack("p", data, offset + pos - 1) - end - if ( bit.band( bitmap, FILE_BITMAP.ExtendedResourceForkSize ) == FILE_BITMAP.ExtendedResourceForkSize ) then - pos, file.ExtendedResourceForkSize = bin.unpack(">L", data, pos ) - end - if ( bit.band( bitmap, FILE_BITMAP.UnixPrivileges ) == FILE_BITMAP.UnixPrivileges ) then - local unixprivs = {} - pos, unixprivs.uid, unixprivs.gid, - unixprivs.permissions, unixprivs.ua_permissions = bin.unpack(">IIII", data, pos ) - file.UnixPrivileges = unixprivs - end - return pos, file - end, + if ( bit.band( bitmap, FILE_BITMAP.Attributes ) == FILE_BITMAP.Attributes ) then + pos, file.Attributes = bin.unpack(">S", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.ParentDirId ) == FILE_BITMAP.ParentDirId ) then + pos, file.ParentDirId = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.CreationDate ) == FILE_BITMAP.CreationDate ) then + pos, file.CreationDate = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.ModificationDate ) == FILE_BITMAP.ModificationDate ) then + pos, file.ModificationDate = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.BackupDate ) == FILE_BITMAP.BackupDate ) then + pos, file.BackupDate = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.FinderInfo ) == FILE_BITMAP.FinderInfo ) then + pos, file.FinderInfo = bin.unpack("A32", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.LongName ) == FILE_BITMAP.LongName ) then + local offset, p, name + pos, offset = bin.unpack(">S", data, pos) + p, file.LongName = bin.unpack("p", data, offset + pos - 1) + end + if ( bit.band( bitmap, FILE_BITMAP.ShortName ) == FILE_BITMAP.ShortName ) then + local offset, p, name + pos, offset = bin.unpack(">S", data, pos) + p, file.ShortName = bin.unpack("p", data, offset + pos - 1) + end + if ( bit.band( bitmap, FILE_BITMAP.NodeId ) == FILE_BITMAP.NodeId ) then + pos, file.NodeId = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.DataForkSize ) == FILE_BITMAP.DataForkSize ) then + pos, file.DataForkSize = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.ResourceForkSize ) == FILE_BITMAP.ResourceForkSize ) then + pos, file.ResourceForkSize = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.ExtendedDataForkSize ) == FILE_BITMAP.ExtendedDataForkSize ) then + pos, file.ExtendedDataForkSize = bin.unpack(">L", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.LaunchLimit ) == FILE_BITMAP.LaunchLimit ) then + -- should not be set as it's deprecated according to: + -- http://developer.apple.com/mac/library/documentation/Networking/Reference/AFP_Reference/Reference/reference.html#//apple_ref/doc/c_ref/kFPLaunchLimitBit + end + if ( bit.band( bitmap, FILE_BITMAP.UTF8Name ) == FILE_BITMAP.UTF8Name ) then + local offset, p, name + pos, offset = bin.unpack(">S", data, pos) + p, file.UTF8Name = bin.unpack("p", data, offset + pos - 1) + end + if ( bit.band( bitmap, FILE_BITMAP.ExtendedResourceForkSize ) == FILE_BITMAP.ExtendedResourceForkSize ) then + pos, file.ExtendedResourceForkSize = bin.unpack(">L", data, pos ) + end + if ( bit.band( bitmap, FILE_BITMAP.UnixPrivileges ) == FILE_BITMAP.UnixPrivileges ) then + local unixprivs = {} + pos, unixprivs.uid, unixprivs.gid, + unixprivs.permissions, unixprivs.ua_permissions = bin.unpack(">IIII", data, pos ) + file.UnixPrivileges = unixprivs + end + return pos, file + end, - --- Decodes a directory bitmap - -- - -- @param bitmap number containing the bitmap - -- @param data string containing the data to be decoded - -- @param pos number containing the offset into data - -- @return pos number containing the new offset after decoding - -- @return dir table containing the decoded values - decode_dir_bitmap = function( bitmap, data, pos ) - local dir = {} + --- Decodes a directory bitmap + -- + -- @param bitmap number containing the bitmap + -- @param data string containing the data to be decoded + -- @param pos number containing the offset into data + -- @return pos number containing the new offset after decoding + -- @return dir table containing the decoded values + decode_dir_bitmap = function( bitmap, data, pos ) + local dir = {} - if ( bit.band( bitmap, DIR_BITMAP.Attributes ) == DIR_BITMAP.Attributes ) then - pos, dir.Attributes = bin.unpack(">S", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.ParentDirId ) == DIR_BITMAP.ParentDirId ) then - pos, dir.ParentDirId = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.CreationDate ) == DIR_BITMAP.CreationDate ) then - pos, dir.CreationDate = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.ModificationDate ) == DIR_BITMAP.ModificationDate ) then - pos, dir.ModificationDate = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.BackupDate ) == DIR_BITMAP.BackupDate ) then - pos, dir.BackupDate = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.FinderInfo ) == DIR_BITMAP.FinderInfo ) then - pos, dir.FinderInfo = bin.unpack("A32", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.LongName ) == DIR_BITMAP.LongName ) then - local offset, p, name - pos, offset = bin.unpack(">S", data, pos) + if ( bit.band( bitmap, DIR_BITMAP.Attributes ) == DIR_BITMAP.Attributes ) then + pos, dir.Attributes = bin.unpack(">S", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.ParentDirId ) == DIR_BITMAP.ParentDirId ) then + pos, dir.ParentDirId = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.CreationDate ) == DIR_BITMAP.CreationDate ) then + pos, dir.CreationDate = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.ModificationDate ) == DIR_BITMAP.ModificationDate ) then + pos, dir.ModificationDate = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.BackupDate ) == DIR_BITMAP.BackupDate ) then + pos, dir.BackupDate = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.FinderInfo ) == DIR_BITMAP.FinderInfo ) then + pos, dir.FinderInfo = bin.unpack("A32", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.LongName ) == DIR_BITMAP.LongName ) then + local offset, p, name + pos, offset = bin.unpack(">S", data, pos) - -- TODO: This really needs to be adressed someway - -- Barely, never, ever happens, which makes it difficult to pin down - -- http://developer.apple.com/mac/library/documentation/Networking/Reference/ - -- AFP_Reference/Reference/reference.html#//apple_ref/doc/uid/TP40003548-CH3-CHDBEHBG [URL is wrapped] - local justkidding = select(2, bin.unpack(">I", data, pos + 4)) - if ( justkidding ~= 0 ) then - offset = 5 - end + -- TODO: This really needs to be adressed someway + -- Barely, never, ever happens, which makes it difficult to pin down + -- http://developer.apple.com/mac/library/documentation/Networking/Reference/ + -- AFP_Reference/Reference/reference.html#//apple_ref/doc/uid/TP40003548-CH3-CHDBEHBG [URL is wrapped] + local justkidding = select(2, bin.unpack(">I", data, pos + 4)) + if ( justkidding ~= 0 ) then + offset = 5 + end - p, dir.LongName = bin.unpack("p", data, offset + pos - 1) - end - if ( bit.band( bitmap, DIR_BITMAP.ShortName ) == DIR_BITMAP.ShortName ) then - local offset, p, name - pos, offset = bin.unpack(">S", data, pos) - p, dir.ShortName = bin.unpack("p", data, offset + pos - 1) - end - if ( bit.band( bitmap, DIR_BITMAP.NodeId ) == DIR_BITMAP.NodeId ) then - pos, dir.NodeId = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.OffspringCount ) == DIR_BITMAP.OffspringCount ) then - pos, dir.OffspringCount = bin.unpack(">S", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.OwnerId ) == DIR_BITMAP.OwnerId ) then - pos, dir.OwnerId = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.GroupId ) == DIR_BITMAP.GroupId ) then - pos, dir.GroupId = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.AccessRights ) == DIR_BITMAP.AccessRights ) then - pos, dir.AccessRights = bin.unpack(">I", data, pos ) - end - if ( bit.band( bitmap, DIR_BITMAP.UTF8Name ) == DIR_BITMAP.UTF8Name ) then - local offset, p, name - pos, offset = bin.unpack(">S", data, pos) - p, dir.UTF8Name = bin.unpack("p", data, offset + pos - 1) - end - if ( bit.band( bitmap, DIR_BITMAP.UnixPrivileges ) == DIR_BITMAP.UnixPrivileges ) then - local unixprivs = {} + p, dir.LongName = bin.unpack("p", data, offset + pos - 1) + end + if ( bit.band( bitmap, DIR_BITMAP.ShortName ) == DIR_BITMAP.ShortName ) then + local offset, p, name + pos, offset = bin.unpack(">S", data, pos) + p, dir.ShortName = bin.unpack("p", data, offset + pos - 1) + end + if ( bit.band( bitmap, DIR_BITMAP.NodeId ) == DIR_BITMAP.NodeId ) then + pos, dir.NodeId = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.OffspringCount ) == DIR_BITMAP.OffspringCount ) then + pos, dir.OffspringCount = bin.unpack(">S", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.OwnerId ) == DIR_BITMAP.OwnerId ) then + pos, dir.OwnerId = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.GroupId ) == DIR_BITMAP.GroupId ) then + pos, dir.GroupId = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.AccessRights ) == DIR_BITMAP.AccessRights ) then + pos, dir.AccessRights = bin.unpack(">I", data, pos ) + end + if ( bit.band( bitmap, DIR_BITMAP.UTF8Name ) == DIR_BITMAP.UTF8Name ) then + local offset, p, name + pos, offset = bin.unpack(">S", data, pos) + p, dir.UTF8Name = bin.unpack("p", data, offset + pos - 1) + end + if ( bit.band( bitmap, DIR_BITMAP.UnixPrivileges ) == DIR_BITMAP.UnixPrivileges ) then + local unixprivs = {} - pos, unixprivs.uid, unixprivs.gid, - unixprivs.permissions, unixprivs.ua_permissions = bin.unpack(">I>I>I>I", data, pos ) - dir.UnixPrivileges = unixprivs - end - return pos, dir - end, + pos, unixprivs.uid, unixprivs.gid, + unixprivs.permissions, unixprivs.ua_permissions = bin.unpack(">I>I>I>I", data, pos ) + dir.UnixPrivileges = unixprivs + end + return pos, dir + end, } diff --git a/nselib/ajp.lua b/nselib/ajp.lua index c65ba0ecc..becd4c964 100644 --- a/nselib/ajp.lua +++ b/nselib/ajp.lua @@ -18,507 +18,507 @@ _ENV = stdnse.module("ajp", stdnse.seeall) AJP = { - -- The magic prefix that has to be present in all requests - Magic = 0x1234, + -- The magic prefix that has to be present in all requests + Magic = 0x1234, - -- Methods encoded as numeric values - Method = { - ['OPTIONS'] = 1, - ['GET'] = 2, - ['HEAD'] = 3, - ['POST'] = 4, - ['PUT'] = 5, - ['DELETE'] = 6, - ['TRACE'] = 7, - ['PROPFIND'] = 8, - ['PROPPATCH'] = 9, - ['MKCOL'] = 10, - ['COPY'] = 11, - ['MOVE'] = 12, - ['LOCK'] = 13, - ['UNLOCK'] = 14, - ['ACL'] = 15, - ['REPORT'] = 16, - ['VERSION-CONTROL'] = 17, - ['CHECKIN'] = 18, - ['CHECKOUT'] = 19, - ['UNCHECKOUT'] = 20, - ['SEARCH'] = 21, - ['MKWORKSPACE'] = 22, - ['UPDATE'] = 23, - ['LABEL'] = 24, - ['MERGE'] = 25, - ['BASELINE_CONTROL'] = 26, - ['MKACTIVITY'] = 27, - }, + -- Methods encoded as numeric values + Method = { + ['OPTIONS'] = 1, + ['GET'] = 2, + ['HEAD'] = 3, + ['POST'] = 4, + ['PUT'] = 5, + ['DELETE'] = 6, + ['TRACE'] = 7, + ['PROPFIND'] = 8, + ['PROPPATCH'] = 9, + ['MKCOL'] = 10, + ['COPY'] = 11, + ['MOVE'] = 12, + ['LOCK'] = 13, + ['UNLOCK'] = 14, + ['ACL'] = 15, + ['REPORT'] = 16, + ['VERSION-CONTROL'] = 17, + ['CHECKIN'] = 18, + ['CHECKOUT'] = 19, + ['UNCHECKOUT'] = 20, + ['SEARCH'] = 21, + ['MKWORKSPACE'] = 22, + ['UPDATE'] = 23, + ['LABEL'] = 24, + ['MERGE'] = 25, + ['BASELINE_CONTROL'] = 26, + ['MKACTIVITY'] = 27, + }, - -- Request codes - Code = { - FORWARD_REQUEST = 2, - SEND_BODY = 3, - SEND_HEADERS = 4, - END_RESPONSE = 5, - SHUTDOWN = 7, - PING = 8, - CPING = 10, - }, + -- Request codes + Code = { + FORWARD_REQUEST = 2, + SEND_BODY = 3, + SEND_HEADERS = 4, + END_RESPONSE = 5, + SHUTDOWN = 7, + PING = 8, + CPING = 10, + }, - -- Request attributes - Attribute = { - CONTEXT = 0x01, - SERVLET_PATH = 0x02, - REMOTE_USER = 0x03, - AUTH_TYPE = 0x04, - QUERY_STRING = 0x05, - JVM_ROUTE = 0x06, - SSL_CERT = 0x07, - SSL_CIPHER = 0x08, - SSL_SESSION = 0x09, - REQ_ATTRIBUTE= 0x0A, - SSL_KEY_SIZE = 0x0B, - ARE_DONE = 0xFF, - }, + -- Request attributes + Attribute = { + CONTEXT = 0x01, + SERVLET_PATH = 0x02, + REMOTE_USER = 0x03, + AUTH_TYPE = 0x04, + QUERY_STRING = 0x05, + JVM_ROUTE = 0x06, + SSL_CERT = 0x07, + SSL_CIPHER = 0x08, + SSL_SESSION = 0x09, + REQ_ATTRIBUTE= 0x0A, + SSL_KEY_SIZE = 0x0B, + ARE_DONE = 0xFF, + }, - ForwardRequest = { + ForwardRequest = { - -- Common headers encoded as numeric values - Header = { - ['accept'] = 0xA001, - ['accept-charset'] = 0xA002, - ['accept-encoding'] = 0xA003, - ['accept-language'] = 0xA004, - ['authorization'] = 0xA005, - ['connection'] = 0xA006, - ['content-type'] = 0xA007, - ['content-length'] = 0xA008, - ['cookie'] = 0xA009, - ['cookie2'] = 0xA00A, - ['host'] = 0xA00B, - ['pragma'] = 0xA00C, - ['referer'] = 0xA00D, - ['user-agent'] = 0xA00E, - }, + -- Common headers encoded as numeric values + Header = { + ['accept'] = 0xA001, + ['accept-charset'] = 0xA002, + ['accept-encoding'] = 0xA003, + ['accept-language'] = 0xA004, + ['authorization'] = 0xA005, + ['connection'] = 0xA006, + ['content-type'] = 0xA007, + ['content-length'] = 0xA008, + ['cookie'] = 0xA009, + ['cookie2'] = 0xA00A, + ['host'] = 0xA00B, + ['pragma'] = 0xA00C, + ['referer'] = 0xA00D, + ['user-agent'] = 0xA00E, + }, - new = function(self, host, port, method, uri, headers, attributes, options) - local o = { - host = host, - magic = 0x1234, - length = 0, - code = AJP.Code.FORWARD_REQUEST, - method = AJP.Method[method], - version = "HTTP/1.1", - uri = uri, - raddr = options.raddr or "127.0.0.1", - rhost = options.rhost or "", - srv = host.ip, - port = port.number, - is_ssl = (port.service == "https"), - headers = headers or {}, - attributes = attributes or {}, - } - setmetatable(o, self) - self.__index = self + new = function(self, host, port, method, uri, headers, attributes, options) + local o = { + host = host, + magic = 0x1234, + length = 0, + code = AJP.Code.FORWARD_REQUEST, + method = AJP.Method[method], + version = "HTTP/1.1", + uri = uri, + raddr = options.raddr or "127.0.0.1", + rhost = options.rhost or "", + srv = host.ip, + port = port.number, + is_ssl = (port.service == "https"), + headers = headers or {}, + attributes = attributes or {}, + } + setmetatable(o, self) + self.__index = self return o - end, + end, - __tostring = function(self) + __tostring = function(self) - -- encodes a string, prefixing it with a 2-byte length - -- and suffixing it with a zero. P-encoding can't be used - -- as the zero terminator should not be counted in the length - local function encstr(str) - if ( not(str) or #str == 0 ) then - return bin.pack(">S", 0xFFFF) - end - return bin.pack(">Sz", #str, str) - end + -- encodes a string, prefixing it with a 2-byte length + -- and suffixing it with a zero. P-encoding can't be used + -- as the zero terminator should not be counted in the length + local function encstr(str) + if ( not(str) or #str == 0 ) then + return bin.pack(">S", 0xFFFF) + end + return bin.pack(">Sz", #str, str) + end - -- count the number of headers - local function headerCount() - local i = 0 - for _, _ in pairs(self.headers) do i = i + 1 end - return i - end + -- count the number of headers + local function headerCount() + local i = 0 + for _, _ in pairs(self.headers) do i = i + 1 end + return i + end - -- add host header if it's missing - if ( not(self.headers['host']) ) then - self.headers['host'] = stdnse.get_hostname(self.host) - end + -- add host header if it's missing + if ( not(self.headers['host']) ) then + self.headers['host'] = stdnse.get_hostname(self.host) + end - -- add keep-alive connection header if missing - if ( not(self.headers['connection']) ) then - self.headers['connection'] = "keep-alive" - end + -- add keep-alive connection header if missing + if ( not(self.headers['connection']) ) then + self.headers['connection'] = "keep-alive" + end - local p_url = url.parse(self.uri) + local p_url = url.parse(self.uri) - -- save the magic and data for last - local data = bin.pack(">CCAAAAASCS", self.code, self.method, - encstr(self.version), encstr(p_url.path), encstr(self.raddr), - encstr(self.rhost), encstr(self.srv), - self.port, (self.is_ssl and 1 or 0), - headerCount()) + -- save the magic and data for last + local data = bin.pack(">CCAAAAASCS", self.code, self.method, + encstr(self.version), encstr(p_url.path), encstr(self.raddr), + encstr(self.rhost), encstr(self.srv), + self.port, (self.is_ssl and 1 or 0), + headerCount()) - -- encode headers - for k, v in pairs(self.headers) do - local header = AJP.ForwardRequest.Header[k:lower()] or k - if ( "string" == type(header) ) then - data = data .. bin.pack(">Sz", #header, header) - else - data = data .. bin.pack(">S", header) - end + -- encode headers + for k, v in pairs(self.headers) do + local header = AJP.ForwardRequest.Header[k:lower()] or k + if ( "string" == type(header) ) then + data = data .. bin.pack(">Sz", #header, header) + else + data = data .. bin.pack(">S", header) + end - data = data .. encstr(v) - end + data = data .. encstr(v) + end - -- encode attributes - if ( p_url.query ) then - data = data .. bin.pack("C", AJP.Attribute.QUERY_STRING) - data = data .. encstr(p_url.query) - end + -- encode attributes + if ( p_url.query ) then + data = data .. bin.pack("C", AJP.Attribute.QUERY_STRING) + data = data .. encstr(p_url.query) + end - -- terminate the attribute list - data = data .. bin.pack("C", AJP.Attribute.ARE_DONE) + -- terminate the attribute list + data = data .. bin.pack("C", AJP.Attribute.ARE_DONE) - -- returns the AJP request as a string - return bin.pack(">SSA", AJP.Magic, #data, data) - end, + -- returns the AJP request as a string + return bin.pack(">SSA", AJP.Magic, #data, data) + end, - }, + }, - Response = { + Response = { - Header = { - ['Content-Type'] = 0xA001, - ['Content-Language'] = 0xA002, - ['Content-Length'] = 0xA003, - ['Date'] = 0xA004, - ['Last-Modified'] = 0xA005, - ['Location'] = 0xA006, - ['Set-Cookie'] = 0xA007, - ['Set-Cookie2'] = 0xA008, - ['Servlet-Engine'] = 0xA009, - ['Status'] = 0xA00A, - ['WWW-Authenticate'] = 0xA00B, - }, + Header = { + ['Content-Type'] = 0xA001, + ['Content-Language'] = 0xA002, + ['Content-Length'] = 0xA003, + ['Date'] = 0xA004, + ['Last-Modified'] = 0xA005, + ['Location'] = 0xA006, + ['Set-Cookie'] = 0xA007, + ['Set-Cookie2'] = 0xA008, + ['Servlet-Engine'] = 0xA009, + ['Status'] = 0xA00A, + ['WWW-Authenticate'] = 0xA00B, + }, - SendHeaders = { + SendHeaders = { - new = function(self) - local o = { headers = {}, rawheaders = {} } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { headers = {}, rawheaders = {} } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local sh = AJP.Response.SendHeaders:new() - local pos = 6 - local status_msg, hdr_count + parse = function(data) + local sh = AJP.Response.SendHeaders:new() + local pos = 6 + local status_msg, hdr_count - pos, sh.status = bin.unpack(">S", data, pos) - pos, status_msg = bin.unpack(">P", data, pos) - pos = pos + 1 - sh['status-line'] = ("AJP/1.3 %d %s"):format(sh.status, status_msg) + pos, sh.status = bin.unpack(">S", data, pos) + pos, status_msg = bin.unpack(">P", data, pos) + pos = pos + 1 + sh['status-line'] = ("AJP/1.3 %d %s"):format(sh.status, status_msg) - pos, hdr_count = bin.unpack(">S", data, pos) + pos, hdr_count = bin.unpack(">S", data, pos) - local function headerById(id) - for k, v in pairs(AJP.Response.Header) do - if ( v == id ) then return k end - end - end + local function headerById(id) + for k, v in pairs(AJP.Response.Header) do + if ( v == id ) then return k end + end + end - for i=1, hdr_count do - local key, val, len - pos, len = bin.unpack(">S", data, pos) + for i=1, hdr_count do + local key, val, len + pos, len = bin.unpack(">S", data, pos) - if ( len < 0xA000 ) then - pos, key = bin.unpack("A"..len, data, pos) - pos = pos + 1 - else - key = headerById(len) - end + if ( len < 0xA000 ) then + pos, key = bin.unpack("A"..len, data, pos) + pos = pos + 1 + else + key = headerById(len) + end - pos, val = bin.unpack(">P", data, pos) - pos = pos + 1 + pos, val = bin.unpack(">P", data, pos) + pos = pos + 1 - sh.headers[key:lower()] = val + sh.headers[key:lower()] = val - -- to keep the order, in which the headers were received, - -- add them to the rawheader table as well. This is based - -- on the same principle as the http library, however the - -- difference being that we have to "construct" the "raw" - -- format of the header, as we're receiving kvp's. - table.insert(sh.rawheaders, ("%s: %s"):format(key,val)) - end - return sh - end, + -- to keep the order, in which the headers were received, + -- add them to the rawheader table as well. This is based + -- on the same principle as the http library, however the + -- difference being that we have to "construct" the "raw" + -- format of the header, as we're receiving kvp's. + table.insert(sh.rawheaders, ("%s: %s"):format(key,val)) + end + return sh + end, - }, + }, - }, + }, } -- The Comm class handles sending and receiving AJP requests/responses Comm = { - -- Creates a new Comm instance - new = function(self, host, port, options) - local o = { host = host, port = port, options = options or {}} - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Comm instance + new = function(self, host, port, options) + local o = { host = host, port = port, options = options or {}} + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects to the AJP server - -- - -- @return status true on success, false on failure - -- @return err string containing error message on failure - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(self.options.timeout or 5000) - return self.socket:connect(self.host, self.port) - end, + -- Connects to the AJP server + -- + -- @return status true on success, false on failure + -- @return err string containing error message on failure + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(self.options.timeout or 5000) + return self.socket:connect(self.host, self.port) + end, - -- Sends a request to the server - -- - -- @param req instance of object that can be serialized with tostring - -- @return status true on succes, false on failure - -- @return err string containing error message on failure - send = function(self, req) - return self.socket:send(tostring(req)) - end, + -- Sends a request to the server + -- + -- @param req instance of object that can be serialized with tostring + -- @return status true on succes, false on failure + -- @return err string containing error message on failure + send = function(self, req) + return self.socket:send(tostring(req)) + end, - -- Receives an AJP response from the server - -- - -- @return status true on succes, false on failure - -- @return response table containing the following fields, or string - -- containing error message on failure - -- status - status of response (see HTTP status codes) - -- status-line - the complete status line (eg. 200 OK) - -- body - the response body as string - -- headers - table of response headers - -- - receive = function(self) - local response = {} - while(true) do - local status, buf = self.socket:receive_buf(match.numbytes(4), true) - if ( not(status) ) then - return false, "Failed to receive response from server" - end - local pos, magic, length = bin.unpack(">A2S", buf) - if ( magic ~= "AB" ) then - return false, ("Invalid magic received from server (%s)"):format(magic) - end - local status, data = self.socket:receive_buf(match.numbytes(length), true) - if ( not(status) ) then - return false, "Failed to receive response from server" - end + -- Receives an AJP response from the server + -- + -- @return status true on succes, false on failure + -- @return response table containing the following fields, or string + -- containing error message on failure + -- status - status of response (see HTTP status codes) + -- status-line - the complete status line (eg. 200 OK) + -- body - the response body as string + -- headers - table of response headers + -- + receive = function(self) + local response = {} + while(true) do + local status, buf = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to receive response from server" + end + local pos, magic, length = bin.unpack(">A2S", buf) + if ( magic ~= "AB" ) then + return false, ("Invalid magic received from server (%s)"):format(magic) + end + local status, data = self.socket:receive_buf(match.numbytes(length), true) + if ( not(status) ) then + return false, "Failed to receive response from server" + end - local pos, code = bin.unpack("C", data) - if ( AJP.Code.SEND_HEADERS == code ) then - local sh = AJP.Response.SendHeaders.parse(buf .. data) - response = sh - elseif( AJP.Code.SEND_BODY == code ) then - response.body = select(2, bin.unpack(">P", data, pos)) - elseif( AJP.Code.END_RESPONSE == code ) then - break - end - end - return true, response - end, + local pos, code = bin.unpack("C", data) + if ( AJP.Code.SEND_HEADERS == code ) then + local sh = AJP.Response.SendHeaders.parse(buf .. data) + response = sh + elseif( AJP.Code.SEND_BODY == code ) then + response.body = select(2, bin.unpack(">P", data, pos)) + elseif( AJP.Code.END_RESPONSE == code ) then + break + end + end + return true, response + end, - -- Closes the socket - close = function(self) - return self.socket:close() - end, + -- Closes the socket + close = function(self) + return self.socket:close() + end, } Helper = { - --- Creates a new AJP Helper instance - -- - -- @param host table - -- @param port table - -- @param opt - -- @return o new Helper instance - new = function(self, host, port, opt) - local o = { host = host, port = port, opt = opt or {} } - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new AJP Helper instance + -- + -- @param host table + -- @param port table + -- @param opt + -- @return o new Helper instance + new = function(self, host, port, opt) + local o = { host = host, port = port, opt = opt or {} } + setmetatable(o, self) + self.__index = self + return o + end, - --- Connects to the AJP server - -- - -- @return status true on success, false on failure - -- @return err string containing error message on failure - connect = function(self) - self.comm = Comm:new(self.host, self.port, self.opt) - return self.comm:connect() - end, + --- Connects to the AJP server + -- + -- @return status true on success, false on failure + -- @return err string containing error message on failure + connect = function(self) + self.comm = Comm:new(self.host, self.port, self.opt) + return self.comm:connect() + end, - getOption = function(self, options, key) + getOption = function(self, options, key) - -- first check options, then global self.opt - if ( options and options[key] ) then - return options[key] - elseif ( self.opt and self.opt[key] ) then - return self.opt[key] - end + -- first check options, then global self.opt + if ( options and options[key] ) then + return options[key] + elseif ( self.opt and self.opt[key] ) then + return self.opt[key] + end - end, + end, - --- Sends an AJP request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - request = function(self, method, url, headers, attributes, options) - local status, lhost, lport, rhost, rport = self.comm.socket:get_info() - if ( not(status) ) then - return false, "Failed to get socket information" - end + --- Sends an AJP request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + request = function(self, method, url, headers, attributes, options) + local status, lhost, lport, rhost, rport = self.comm.socket:get_info() + if ( not(status) ) then + return false, "Failed to get socket information" + end - local request = AJP.ForwardRequest:new(self.host, self.port, method, url, headers, attributes, { raddr = rhost }) - if ( not(self.comm:send(request)) ) then - return false, "Failed to send request to server" - end - local status, result = self.comm:receive() + local request = AJP.ForwardRequest:new(self.host, self.port, method, url, headers, attributes, { raddr = rhost }) + if ( not(self.comm:send(request)) ) then + return false, "Failed to send request to server" + end + local status, result = self.comm:receive() - -- support Basic authentication - if ( status and result.status == 401 and result.headers['www-authenticate'] ) then + -- support Basic authentication + if ( status and result.status == 401 and result.headers['www-authenticate'] ) then - local auth = self:getOption(options, "auth") - if ( not(auth) or not(auth.username) and not(auth.password) ) then - stdnse.print_debug(2, "No authentication information") - return status, result - end + local auth = self:getOption(options, "auth") + if ( not(auth) or not(auth.username) and not(auth.password) ) then + stdnse.print_debug(2, "No authentication information") + return status, result + end - local challenges = http.parse_www_authenticate(result.headers['www-authenticate']) - local scheme - for _, challenge in ipairs(challenges or {}) do - if ( challenge and challenge.scheme and challenge.scheme:lower() == "basic") then - scheme = challenge.scheme:lower() - break - end - end + local challenges = http.parse_www_authenticate(result.headers['www-authenticate']) + local scheme + for _, challenge in ipairs(challenges or {}) do + if ( challenge and challenge.scheme and challenge.scheme:lower() == "basic") then + scheme = challenge.scheme:lower() + break + end + end - if ( not(scheme) ) then - stdnse.print_debug(2, "Could not find a supported authentication scheme") - elseif ( "basic" ~= scheme ) then - stdnse.print_debug(2, "Unsupported authentication scheme: %s", scheme) - else - headers = headers or {} - headers["Authorization"] = ("Basic %s"):format(base64.enc(auth.username .. ":" .. auth.password)) - request = AJP.ForwardRequest:new(self.host, self.port, method, url, headers, attributes, { raddr = rhost }) - if ( not(self.comm:send(request)) ) then - return false, "Failed to send request to server" - end - status, result = self.comm:receive() - end + if ( not(scheme) ) then + stdnse.print_debug(2, "Could not find a supported authentication scheme") + elseif ( "basic" ~= scheme ) then + stdnse.print_debug(2, "Unsupported authentication scheme: %s", scheme) + else + headers = headers or {} + headers["Authorization"] = ("Basic %s"):format(base64.enc(auth.username .. ":" .. auth.password)) + request = AJP.ForwardRequest:new(self.host, self.port, method, url, headers, attributes, { raddr = rhost }) + if ( not(self.comm:send(request)) ) then + return false, "Failed to send request to server" + end + status, result = self.comm:receive() + end - end - return status, result - end, + end + return status, result + end, - --- Sends an AJP GET request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - get = function(self, url, headers, attributes, options) - return self:request("GET", url, headers, attributes, options) - end, + --- Sends an AJP GET request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + get = function(self, url, headers, attributes, options) + return self:request("GET", url, headers, attributes, options) + end, - --- Sends an AJP HEAD request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - head = function(self, url, headers, attributes, options) - return self:request("HEAD", url, headers, attributes, options) - end, + --- Sends an AJP HEAD request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + head = function(self, url, headers, attributes, options) + return self:request("HEAD", url, headers, attributes, options) + end, - --- Sends an AJP TRACE request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - trace = function(self, url, headers, attributes, options) - return self:request("TRACE", url, headers, attributes, options) - end, + --- Sends an AJP TRACE request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + trace = function(self, url, headers, attributes, options) + return self:request("TRACE", url, headers, attributes, options) + end, - --- Sends an AJP PUT request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - put = function(self, url, headers, attributes, options) - return self:request("PUT", url, headers, attributes, options) - end, + --- Sends an AJP PUT request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + put = function(self, url, headers, attributes, options) + return self:request("PUT", url, headers, attributes, options) + end, - --- Sends an AJP DELETE request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - delete = function(self, url, headers, attributes, options) - return self:request("DELETE", url, headers, attributes, options) - end, + --- Sends an AJP DELETE request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + delete = function(self, url, headers, attributes, options) + return self:request("DELETE", url, headers, attributes, options) + end, - --- Sends an AJP OPTIONS request to the server - -- - -- @param url string containing the URL to query - -- @param headers table containing optional headers - -- @param attributes table containing optional attributes - -- @param options table with request specific options - -- @return status true on succes, false on failure - -- @return response table (@see Comm.receive), or string containing error - -- message on failure - options = function(self, url, headers, attributes, options) - return self:request("OPTIONS", url, headers, attributes, options) - end, + --- Sends an AJP OPTIONS request to the server + -- + -- @param url string containing the URL to query + -- @param headers table containing optional headers + -- @param attributes table containing optional attributes + -- @param options table with request specific options + -- @return status true on succes, false on failure + -- @return response table (@see Comm.receive), or string containing error + -- message on failure + options = function(self, url, headers, attributes, options) + return self:request("OPTIONS", url, headers, attributes, options) + end, - -- should only work against 127.0.0.1 - shutdownContainer = function(self) - self.comm:send(bin.pack("H", "1234000107")) - self.comm:receive() - end, + -- should only work against 127.0.0.1 + shutdownContainer = function(self) + self.comm:send(bin.pack("H", "1234000107")) + self.comm:receive() + end, - --- Disconnects from the server - close = function(self) - return self.comm:close() - end, + --- Disconnects from the server + close = function(self) + return self.comm:close() + end, } diff --git a/nselib/amqp.lua b/nselib/amqp.lua index 17f2d6d84..aa2a741d5 100644 --- a/nselib/amqp.lua +++ b/nselib/amqp.lua @@ -66,7 +66,7 @@ AMQP = { local data, status, msg status, msg = self.amqpsocket:connect(self.host, self.port, "tcp") - return status, msg + return status, msg end, --- Disconnects the AMQP socket @@ -337,7 +337,7 @@ AMQPSocket = retries = 3, new = function(self) - local o = {} + local o = {} setmetatable(o, self) self.__index = self o.Socket = nmap.new_socket() @@ -371,7 +371,7 @@ AMQPSocket = -- @param count of bytes to read -- @return true on success, false on failure -- @return data containing bytes read from the socket - -- err containing error message if status is false + -- err containing error message if status is false recv = function( self, count ) local status, data diff --git a/nselib/asn1.lua b/nselib/asn1.lua index b285eda5b..2dc7f0f22 100644 --- a/nselib/asn1.lua +++ b/nselib/asn1.lua @@ -11,8 +11,8 @@ -- Version 0.3 -- Created 01/12/2010 - v0.1 - Created by Patrik Karlsson -- Revised 01/28/2010 - v0.2 - Adapted to create a framework for SNMP, LDAP and future protocols --- Revised 02/02/2010 - v0.3 - Changes: o Re-designed so that ASN1Encoder and ASN1Decoder are separate classes --- o Each script or library should now create it's own Encoder and Decoder instance +-- Revised 02/02/2010 - v0.3 - Changes: o Re-designed so that ASN1Encoder and ASN1Decoder are separate classes +-- o Each script or library should now create it's own Encoder and Decoder instance -- local bin = require "bin" @@ -24,252 +24,252 @@ local table = require "table" _ENV = stdnse.module("asn1", stdnse.seeall) BERCLASS = { - Universal = 0, - Application = 64, - ContextSpecific = 128, - Private = 192 + Universal = 0, + Application = 64, + ContextSpecific = 128, + Private = 192 } --- The decoder class -- ASN1Decoder = { - new = function(self,o) - o = o or {} - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self,o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o + end, - --- Tells the decoder to stop if it detects an error while decoding - -- this should probably be the default, but some scripts depend on being - -- able to decode stuff while lacking proper ASN1 decoding functions. - -- - -- @param val boolean, true if decoding should stop on error, - -- otherwise false (default) - setStopOnError = function(self, val) - self.stoponerror = val - end, + --- Tells the decoder to stop if it detects an error while decoding + -- this should probably be the default, but some scripts depend on being + -- able to decode stuff while lacking proper ASN1 decoding functions. + -- + -- @param val boolean, true if decoding should stop on error, + -- otherwise false (default) + setStopOnError = function(self, val) + self.stoponerror = val + end, - --- Registers the base simple type decoders - -- - registerBaseDecoders = function(self) - self.decoder = {} + --- Registers the base simple type decoders + -- + registerBaseDecoders = function(self) + self.decoder = {} - -- Boolean - self.decoder["01"] = function( self, encStr, elen, pos ) - local val = bin.unpack("H", encStr, pos) - if val ~= "FF" then - return pos, true - else - return pos, false - end - end + -- Boolean + self.decoder["01"] = function( self, encStr, elen, pos ) + local val = bin.unpack("H", encStr, pos) + if val ~= "FF" then + return pos, true + else + return pos, false + end + end - -- Integer - self.decoder["02"] = function( self, encStr, elen, pos ) - return self.decodeInt(encStr, elen, pos) - end + -- Integer + self.decoder["02"] = function( self, encStr, elen, pos ) + return self.decodeInt(encStr, elen, pos) + end - -- Octet String - self.decoder["04"] = function( self, encStr, elen, pos ) - return bin.unpack("A" .. elen, encStr, pos) - end + -- Octet String + self.decoder["04"] = function( self, encStr, elen, pos ) + return bin.unpack("A" .. elen, encStr, pos) + end - -- Null - self.decoder["05"] = function( self, encStr, elen, pos ) - return pos, false - end + -- Null + self.decoder["05"] = function( self, encStr, elen, pos ) + return pos, false + end - -- Object Identifier - self.decoder["06"] = function( self, encStr, elen, pos ) - return self:decodeOID( encStr, elen, pos ) - end + -- Object Identifier + self.decoder["06"] = function( self, encStr, elen, pos ) + return self:decodeOID( encStr, elen, pos ) + end - -- Context specific tags - -- - self.decoder["30"] = function( self, encStr, elen, pos ) - return self:decodeSeq(encStr, elen, pos) - end - end, + -- Context specific tags + -- + self.decoder["30"] = function( self, encStr, elen, pos ) + return self:decodeSeq(encStr, elen, pos) + end + end, - --- Allows for registration of additional tag decoders - -- - -- @param tagDecoders table containing decoding functions @see tagDecoders - registerTagDecoders = function(self, tagDecoders) - self:registerBaseDecoders() - for k, v in pairs(tagDecoders) do - self.decoder[k] = v - end - end, + --- Allows for registration of additional tag decoders + -- + -- @param tagDecoders table containing decoding functions @see tagDecoders + registerTagDecoders = function(self, tagDecoders) + self:registerBaseDecoders() + for k, v in pairs(tagDecoders) do + self.decoder[k] = v + end + end, - --- Decodes the ASN.1's built-in simple types - -- - -- @param encStr Encoded string. - -- @param pos Current position in the string. - -- @return The position after decoding - -- @return The decoded value(s). - decode = function(self, encStr, pos) + --- Decodes the ASN.1's built-in simple types + -- + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The position after decoding + -- @return The decoded value(s). + decode = function(self, encStr, pos) - local etype, elen - local newpos = pos + local etype, elen + local newpos = pos - newpos, etype = bin.unpack("H1", encStr, newpos) - newpos, elen = self.decodeLength(encStr, newpos) + newpos, etype = bin.unpack("H1", encStr, newpos) + newpos, elen = self.decodeLength(encStr, newpos) - if self.decoder[etype] then - return self.decoder[etype]( self, encStr, elen, newpos ) - else - stdnse.print_debug("no decoder for etype: " .. etype) - return newpos, nil - end - end, + if self.decoder[etype] then + return self.decoder[etype]( self, encStr, elen, newpos ) + else + stdnse.print_debug("no decoder for etype: " .. etype) + return newpos, nil + end + end, - --- - -- Decodes length part of encoded value according to ASN.1 basic encoding - -- rules. - -- @param encStr Encoded string. - -- @param pos Current position in the string. - -- @return The position after decoding. - -- @return The length of the following value. - decodeLength = function(encStr, pos) - local elen - pos, elen = bin.unpack('C', encStr, pos) - if (elen > 128) then - elen = elen - 128 - local elenCalc = 0 - local elenNext - for i = 1, elen do - elenCalc = elenCalc * 256 - pos, elenNext = bin.unpack("C", encStr, pos) - elenCalc = elenCalc + elenNext - end - elen = elenCalc - end - return pos, elen - end, + --- + -- Decodes length part of encoded value according to ASN.1 basic encoding + -- rules. + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The length of the following value. + decodeLength = function(encStr, pos) + local elen + pos, elen = bin.unpack('C', encStr, pos) + if (elen > 128) then + elen = elen - 128 + local elenCalc = 0 + local elenNext + for i = 1, elen do + elenCalc = elenCalc * 256 + pos, elenNext = bin.unpack("C", encStr, pos) + elenCalc = elenCalc + elenNext + end + elen = elenCalc + end + return pos, elen + end, - --- - -- Decodes a sequence according to ASN.1 basic encoding rules. - -- @param encStr Encoded string. - -- @param len Length of sequence in bytes. - -- @param pos Current position in the string. - -- @return The position after decoding. - -- @return The decoded sequence as a table. - decodeSeq = function(self, encStr, len, pos) - local seq = {} - local sPos = 1 - local sStr - pos, sStr = bin.unpack("A" .. len, encStr, pos) - while (sPos < len) do - local newSeq - sPos, newSeq = self:decode(sStr, sPos) - if ( not(newSeq) and self.stoponerror ) then break end - table.insert(seq, newSeq) - end - return pos, seq - end, + --- + -- Decodes a sequence according to ASN.1 basic encoding rules. + -- @param encStr Encoded string. + -- @param len Length of sequence in bytes. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The decoded sequence as a table. + decodeSeq = function(self, encStr, len, pos) + local seq = {} + local sPos = 1 + local sStr + pos, sStr = bin.unpack("A" .. len, encStr, pos) + while (sPos < len) do + local newSeq + sPos, newSeq = self:decode(sStr, sPos) + if ( not(newSeq) and self.stoponerror ) then break end + table.insert(seq, newSeq) + end + return pos, seq + end, - -- Decode one component of an OID from a byte string. 7 bits of the component - -- are stored in each octet, most significant first, with the eigth bit set in - -- all octets but the last. These encoding rules come from - -- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT - -- IDENTIFIER. - decode_oid_component = function(encStr, pos) - local octet - local n = 0 + -- Decode one component of an OID from a byte string. 7 bits of the component + -- are stored in each octet, most significant first, with the eigth bit set in + -- all octets but the last. These encoding rules come from + -- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT + -- IDENTIFIER. + decode_oid_component = function(encStr, pos) + local octet + local n = 0 - repeat - pos, octet = bin.unpack("C", encStr, pos) - n = n * 128 + bit.band(0x7F, octet) - until octet < 128 + repeat + pos, octet = bin.unpack("C", encStr, pos) + n = n * 128 + bit.band(0x7F, octet) + until octet < 128 - return pos, n - end, + return pos, n + end, - --- Decodes an OID from a sequence of bytes. - -- - -- @param encStr Encoded string. - -- @param len Length of sequence in bytes. - -- @param pos Current position in the string. - -- @return The position after decoding. - -- @return The OID as an array. - decodeOID = function(self, encStr, len, pos) - local last - local oid = {} - local octet + --- Decodes an OID from a sequence of bytes. + -- + -- @param encStr Encoded string. + -- @param len Length of sequence in bytes. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The OID as an array. + decodeOID = function(self, encStr, len, pos) + local last + local oid = {} + local octet - last = pos + len - 1 - if pos <= last then - oid._snmp = '06' - pos, octet = bin.unpack("C", encStr, pos) - oid[2] = math.fmod(octet, 40) - octet = octet - oid[2] - oid[1] = octet/40 - end + last = pos + len - 1 + if pos <= last then + oid._snmp = '06' + pos, octet = bin.unpack("C", encStr, pos) + oid[2] = math.fmod(octet, 40) + octet = octet - oid[2] + oid[1] = octet/40 + end - while pos <= last do - local c - pos, c = self.decode_oid_component(encStr, pos) - oid[#oid + 1] = c - end + while pos <= last do + local c + pos, c = self.decode_oid_component(encStr, pos) + oid[#oid + 1] = c + end - return pos, oid - end, + return pos, oid + end, - --- - -- Decodes length part of encoded value according to ASN.1 basic encoding - -- rules. - -- @param encStr Encoded string. - -- @param pos Current position in the string. - -- @return The position after decoding. - -- @return The length of the following value. - decodeLength = function(encStr, pos) - local elen - pos, elen = bin.unpack('C', encStr, pos) - if (elen > 128) then - elen = elen - 128 - local elenCalc = 0 - local elenNext - for i = 1, elen do - elenCalc = elenCalc * 256 - pos, elenNext = bin.unpack("C", encStr, pos) - elenCalc = elenCalc + elenNext - end - elen = elenCalc - end - return pos, elen - end, + --- + -- Decodes length part of encoded value according to ASN.1 basic encoding + -- rules. + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The length of the following value. + decodeLength = function(encStr, pos) + local elen + pos, elen = bin.unpack('C', encStr, pos) + if (elen > 128) then + elen = elen - 128 + local elenCalc = 0 + local elenNext + for i = 1, elen do + elenCalc = elenCalc * 256 + pos, elenNext = bin.unpack("C", encStr, pos) + elenCalc = elenCalc + elenNext + end + elen = elenCalc + end + return pos, elen + end, - --- - -- Decodes an Integer according to ASN.1 basic encoding rules. - -- @param encStr Encoded string. - -- @param len Length of integer in bytes. - -- @param pos Current position in the string. - -- @return The position after decoding. - -- @return The decoded integer. - decodeInt = function(encStr, len, pos) - local hexStr - pos, hexStr = bin.unpack("H" .. len, encStr, pos) - local value = tonumber(hexStr, 16) - if (value >= math.pow(256, len)/2) then - value = value - math.pow(256, len) - end - return pos, value - end, + --- + -- Decodes an Integer according to ASN.1 basic encoding rules. + -- @param encStr Encoded string. + -- @param len Length of integer in bytes. + -- @param pos Current position in the string. + -- @return The position after decoding. + -- @return The decoded integer. + decodeInt = function(encStr, len, pos) + local hexStr + pos, hexStr = bin.unpack("H" .. len, encStr, pos) + local value = tonumber(hexStr, 16) + if (value >= math.pow(256, len)/2) then + value = value - math.pow(256, len) + end + return pos, value + end, - --- - -- Decodes an SNMP packet or a part of it according to ASN.1 basic encoding - -- rules. - -- @param encStr Encoded string. - -- @param pos Current position in the string. - -- @return The decoded value(s). - dec = function(self, encStr, pos) - local result - local _ - _, result = self:decode(encStr, pos) - return result - end, + --- + -- Decodes an SNMP packet or a part of it according to ASN.1 basic encoding + -- rules. + -- @param encStr Encoded string. + -- @param pos Current position in the string. + -- @return The decoded value(s). + dec = function(self, encStr, pos) + local result + local _ + _, result = self:decode(encStr, pos) + return result + end, } @@ -277,162 +277,162 @@ ASN1Decoder = { -- ASN1Encoder = { - new = function(self) - local o = {} - setmetatable(o, self) - self.__index = self - o:registerBaseEncoders() - return o - end, + new = function(self) + local o = {} + setmetatable(o, self) + self.__index = self + o:registerBaseEncoders() + return o + end, - --- - -- Encodes an ASN1 sequence, the value of 30 below breaks down as - -- 0x30 = 00110000 = 00 1 10000 - -- hex binary Universal Constructed value Data Type = SEQUENCE (16) - encodeSeq = function(self, seqData) - return bin.pack('HAA' , '30', self.encodeLength(#seqData), seqData) - end, + --- + -- Encodes an ASN1 sequence, the value of 30 below breaks down as + -- 0x30 = 00110000 = 00 1 10000 + -- hex binary Universal Constructed value Data Type = SEQUENCE (16) + encodeSeq = function(self, seqData) + return bin.pack('HAA' , '30', self.encodeLength(#seqData), seqData) + end, - --- - -- Encodes a given value according to ASN.1 basic encoding rules for SNMP - -- packet creation. - -- @param val Value to be encoded. - -- @return Encoded value. - encode = function(self, val) - local vtype = type(val) + --- + -- Encodes a given value according to ASN.1 basic encoding rules for SNMP + -- packet creation. + -- @param val Value to be encoded. + -- @return Encoded value. + encode = function(self, val) + local vtype = type(val) - if self.encoder[vtype] then - return self.encoder[vtype](self,val) - else - return nil - end + if self.encoder[vtype] then + return self.encoder[vtype](self,val) + else + return nil + end - return '' - end, + return '' + end, - --- Allows for registration of additional tag encoders - -- - -- @param tagEncoders table containing encoding functions @see tagEncoders - registerTagEncoders = function(self, tagEncoders) - self:registerBaseEncoders() - for k, v in pairs(tagEncoders) do - self.encoder[k] = v - end - end, + --- Allows for registration of additional tag encoders + -- + -- @param tagEncoders table containing encoding functions @see tagEncoders + registerTagEncoders = function(self, tagEncoders) + self:registerBaseEncoders() + for k, v in pairs(tagEncoders) do + self.encoder[k] = v + end + end, - -- ASN.1 Simple types encoders - registerBaseEncoders = function(self) - self.encoder = {} + -- ASN.1 Simple types encoders + registerBaseEncoders = function(self) + self.encoder = {} - -- Bolean encoder - self.encoder['boolean'] = function( self, val ) - if val then - return bin.pack('H','01 01 FF') - else - return bin.pack('H', '01 01 00') - end - end + -- Bolean encoder + self.encoder['boolean'] = function( self, val ) + if val then + return bin.pack('H','01 01 FF') + else + return bin.pack('H', '01 01 00') + end + end - -- Table encoder - self.encoder['table'] = function( self, val ) - assert('table' == type(val), "val is not a table") - assert(#val.type > 0, "Table is missing the type field") - assert(val.value ~= nil, "Table is missing the value field") - return bin.pack("HAA", val.type, self.encodeLength(#val.value), val.value) - end + -- Table encoder + self.encoder['table'] = function( self, val ) + assert('table' == type(val), "val is not a table") + assert(#val.type > 0, "Table is missing the type field") + assert(val.value ~= nil, "Table is missing the value field") + return bin.pack("HAA", val.type, self.encodeLength(#val.value), val.value) + end - -- Integer encoder - self.encoder['number'] = function( self, val ) - local ival = self.encodeInt(val) - local len = self.encodeLength(#ival) - return bin.pack('HAA', '02', len, ival) - end + -- Integer encoder + self.encoder['number'] = function( self, val ) + local ival = self.encodeInt(val) + local len = self.encodeLength(#ival) + return bin.pack('HAA', '02', len, ival) + end - -- Octet String encoder - self.encoder['string'] = function( self, val ) - local len = self.encodeLength(#val) - return bin.pack('HAA', '04', len, val) - end + -- Octet String encoder + self.encoder['string'] = function( self, val ) + local len = self.encodeLength(#val) + return bin.pack('HAA', '04', len, val) + end - -- Null encoder - self.encoder['nil'] = function( self, val ) - return bin.pack('H', '05 00') - end + -- Null encoder + self.encoder['nil'] = function( self, val ) + return bin.pack('H', '05 00') + end - end, + end, - -- Encode one component of an OID as a byte string. 7 bits of the component are - -- stored in each octet, most significant first, with the eigth bit set in all - -- octets but the last. These encoding rules come from - -- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT - -- IDENTIFIER. - encode_oid_component = function(n) - local parts = {} - parts[1] = string.char(bit.mod(n, 128)) - while n >= 128 do - n = bit.rshift(n, 7) - parts[#parts + 1] = string.char(bit.mod(n, 128) + 0x80) - end - return string.reverse(table.concat(parts)) - end, + -- Encode one component of an OID as a byte string. 7 bits of the component are + -- stored in each octet, most significant first, with the eigth bit set in all + -- octets but the last. These encoding rules come from + -- http://luca.ntop.org/Teaching/Appunti/asn1.html, section 5.9 OBJECT + -- IDENTIFIER. + encode_oid_component = function(n) + local parts = {} + parts[1] = string.char(bit.mod(n, 128)) + while n >= 128 do + n = bit.rshift(n, 7) + parts[#parts + 1] = string.char(bit.mod(n, 128) + 0x80) + end + return string.reverse(table.concat(parts)) + end, - --- - -- Encodes an Integer according to ASN.1 basic encoding rules. - -- @param val Value to be encoded. - -- @return Encoded integer. - encodeInt = function(val) - local lsb = 0 - if val > 0 then - local valStr = "" - while (val > 0) do - lsb = math.fmod(val, 256) - valStr = valStr .. bin.pack("C", lsb) - val = math.floor(val/256) - end - if lsb > 127 then -- two's complement collision - valStr = valStr .. bin.pack("H", "00") - end + --- + -- Encodes an Integer according to ASN.1 basic encoding rules. + -- @param val Value to be encoded. + -- @return Encoded integer. + encodeInt = function(val) + local lsb = 0 + if val > 0 then + local valStr = "" + while (val > 0) do + lsb = math.fmod(val, 256) + valStr = valStr .. bin.pack("C", lsb) + val = math.floor(val/256) + end + if lsb > 127 then -- two's complement collision + valStr = valStr .. bin.pack("H", "00") + end - return string.reverse(valStr) - elseif val < 0 then - local i = 1 - local tcval = val + 256 -- two's complement - while tcval <= 127 do - tcval = tcval + (math.pow(256, i) * 255) - i = i+1 - end - local valStr = "" - while (tcval > 0) do - lsb = math.fmod(tcval, 256) - valStr = valStr .. bin.pack("C", lsb) - tcval = math.floor(tcval/256) - end - return string.reverse(valStr) - else -- val == 0 - return bin.pack("x") - end - end, + return string.reverse(valStr) + elseif val < 0 then + local i = 1 + local tcval = val + 256 -- two's complement + while tcval <= 127 do + tcval = tcval + (math.pow(256, i) * 255) + i = i+1 + end + local valStr = "" + while (tcval > 0) do + lsb = math.fmod(tcval, 256) + valStr = valStr .. bin.pack("C", lsb) + tcval = math.floor(tcval/256) + end + return string.reverse(valStr) + else -- val == 0 + return bin.pack("x") + end + end, - --- - -- Encodes the length part of a ASN.1 encoding triplet using the "primitive, - -- definite-length" method. - -- @param len Length to be encoded. - -- @return Encoded length value. - encodeLength = function(len) - if len < 128 then - return string.char(len) - else - local parts = {} + --- + -- Encodes the length part of a ASN.1 encoding triplet using the "primitive, + -- definite-length" method. + -- @param len Length to be encoded. + -- @return Encoded length value. + encodeLength = function(len) + if len < 128 then + return string.char(len) + else + local parts = {} - while len > 0 do - parts[#parts + 1] = string.char(bit.mod(len, 256)) - len = bit.rshift(len, 8) - end + while len > 0 do + parts[#parts + 1] = string.char(bit.mod(len, 256)) + len = bit.rshift(len, 8) + end - assert(#parts < 128) - return string.char(#parts + 0x80) .. string.reverse(table.concat(parts)) - end - end + assert(#parts < 128) + return string.char(#parts + 0x80) .. string.reverse(table.concat(parts)) + end + end } @@ -446,13 +446,13 @@ ASN1Encoder = { -- @return number to be used with encode function BERtoInt(class, constructed, number) - local asn1_type = class + number + local asn1_type = class + number - if constructed == true then - asn1_type = asn1_type + 32 - end + if constructed == true then + asn1_type = asn1_type + 32 + end - return asn1_type + return asn1_type end --- @@ -462,25 +462,25 @@ end -- @return table with the following entries class, constructed, -- primitive and number function intToBER( i ) - local ber = {} + local ber = {} - if bit.band( i, BERCLASS.Application ) == BERCLASS.Application then - ber.class = BERCLASS.Application - elseif bit.band( i, BERCLASS.ContextSpecific ) == BERCLASS.ContextSpecific then - ber.class = BERCLASS.ContextSpecific - elseif bit.band( i, BERCLASS.Private ) == BERCLASS.Private then - ber.class = BERCLASS.Private - else - ber.class = BERCLASS.Universal - end - if bit.band( i, 32 ) == 32 then - ber.constructed = true - ber.number = i - ber.class - 32 - else - ber.primitive = true - ber.number = i - ber.class - end - return ber + if bit.band( i, BERCLASS.Application ) == BERCLASS.Application then + ber.class = BERCLASS.Application + elseif bit.band( i, BERCLASS.ContextSpecific ) == BERCLASS.ContextSpecific then + ber.class = BERCLASS.ContextSpecific + elseif bit.band( i, BERCLASS.Private ) == BERCLASS.Private then + ber.class = BERCLASS.Private + else + ber.class = BERCLASS.Universal + end + if bit.band( i, 32 ) == 32 then + ber.constructed = true + ber.number = i - ber.class - 32 + else + ber.primitive = true + ber.number = i - ber.class + end + return ber end diff --git a/nselib/base32.lua b/nselib/base32.lua index bc85f3e8e..dd4e9fd53 100644 --- a/nselib/base32.lua +++ b/nselib/base32.lua @@ -22,11 +22,11 @@ _ENV = stdnse.module("base32", stdnse.seeall) local b32standard = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', '2', '3', '4', '5', '6', '7', - } + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', '2', '3', '4', '5', '6', '7', +} local b32dcstandard = {} -- efficency b32dcstandard['A'] = '00000' @@ -117,8 +117,8 @@ local concat = table.concat -- @param bits String of five bits to be encoded. -- @return Encoded character. local function b32enc5bit(bits) - local byte = tonumber(bits, 2) + 1 - return b32table[byte] + local byte = tonumber(bits, 2) + 1 + return b32table[byte] end @@ -127,9 +127,9 @@ end -- @param b32byte A single base32-encoded character. -- @return String of five decoded bits. local function b32dec5bit(b32byte) - local bits = b32dctable[b32byte] - if bits then return bits end - return '' + local bits = b32dctable[b32byte] + if bits then return bits end + return '' end @@ -139,32 +139,32 @@ end -- @param hexExtend pass true to use the hex extended char set -- @return Base32-encoded string. function enc(bdata, hexExtend) - local _, bitstring = bunpack(">B".. #bdata,bdata) - local b32dataBuf = {} + local _, bitstring = bunpack(">B".. #bdata,bdata) + local b32dataBuf = {} - if hexExtend then - b32table = b32hexExtend - b32dctable = b32dchexExtend - end + if hexExtend then + b32table = b32hexExtend + b32dctable = b32dchexExtend + end - while #bitstring > 4 do - append(b32dataBuf,b32enc5bit(substr(bitstring,1,5))) - bitstring = substr(bitstring,6) - end - if #bitstring == 1 then - append(b32dataBuf, b32enc5bit(bitstring .. "0000")) - append(b32dataBuf, '====') - elseif #bitstring == 2 then - append(b32dataBuf, b32enc5bit(bitstring .. "000") ) - append(b32dataBuf, '=') - elseif #bitstring == 3 then - append(b32dataBuf, b32enc5bit(bitstring .. "00") ) - append(b32dataBuf, "======") - elseif #bitstring == 4 then - append(b32dataBuf, b32enc5bit(bitstring .. "0") ) - append(b32dataBuf, '===') - end - return concat(b32dataBuf) + while #bitstring > 4 do + append(b32dataBuf,b32enc5bit(substr(bitstring,1,5))) + bitstring = substr(bitstring,6) + end + if #bitstring == 1 then + append(b32dataBuf, b32enc5bit(bitstring .. "0000")) + append(b32dataBuf, '====') + elseif #bitstring == 2 then + append(b32dataBuf, b32enc5bit(bitstring .. "000") ) + append(b32dataBuf, '=') + elseif #bitstring == 3 then + append(b32dataBuf, b32enc5bit(bitstring .. "00") ) + append(b32dataBuf, "======") + elseif #bitstring == 4 then + append(b32dataBuf, b32enc5bit(bitstring .. "0") ) + append(b32dataBuf, '===') + end + return concat(b32dataBuf) end @@ -174,27 +174,27 @@ end -- @param hexExtend pass true to use the hex extended char set -- @return Decoded data. function dec(b32data, hexExtend) - local bdataBuf = {} - local pos = 1 - local byte - local nbyte = '' + local bdataBuf = {} + local pos = 1 + local byte + local nbyte = '' - if hexExtend then - b32table = b32hexExtend - b32dctable = b32dchexExtend - end + if hexExtend then + b32table = b32hexExtend + b32dctable = b32dchexExtend + end - for pos = 1, #b32data do -- while pos <= string.len(b32data) do - byte = b32dec5bit(substr(b32data, pos, pos)) - if not byte then return end - nbyte = nbyte .. byte - if #nbyte >= 8 then - append(bdataBuf, bpack("B", substr(nbyte, 1, 8))) - nbyte = substr(nbyte, 9) - end --- pos = pos + 1 - end - return concat(bdataBuf) + for pos = 1, #b32data do -- while pos <= string.len(b32data) do + byte = b32dec5bit(substr(b32data, pos, pos)) + if not byte then return end + nbyte = nbyte .. byte + if #nbyte >= 8 then + append(bdataBuf, bpack("B", substr(nbyte, 1, 8))) + nbyte = substr(nbyte, 9) + end + -- pos = pos + 1 + end + return concat(bdataBuf) end -return _ENV; \ No newline at end of file +return _ENV; diff --git a/nselib/base64.lua b/nselib/base64.lua index 7d0bed1b1..20601c428 100644 --- a/nselib/base64.lua +++ b/nselib/base64.lua @@ -16,15 +16,15 @@ _ENV = stdnse.module("base64", stdnse.seeall) local b64table = { - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', - 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', - 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', - 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', '0', '1', '2', '3', - '4', '5', '6', '7', '8', '9', '+', '/' - } + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/' +} local b64dctable = {} -- efficency b64dctable['A'] = '000000' @@ -104,13 +104,13 @@ local concat = table.concat -- @param bits String of six bits to be encoded. -- @return Encoded character. local function b64enc6bit(bits) - -- local byte - -- local _, byte = bunpack("C", bpack("B", "00" .. bits)) - -- + -- local byte + -- local _, byte = bunpack("C", bpack("B", "00" .. bits)) + -- - -- more efficient, does the same (nb: add one to byte moved up one line): - local byte = tonumber(bits, 2) + 1 - return b64table[byte] + -- more efficient, does the same (nb: add one to byte moved up one line): + local byte = tonumber(bits, 2) + 1 + return b64table[byte] end @@ -119,9 +119,9 @@ end -- @param b64byte A single base64-encoded character. -- @return String of six decoded bits. local function b64dec6bit(b64byte) - local bits = b64dctable[b64byte] - if bits then return bits end - return '' + local bits = b64dctable[b64byte] + if bits then return bits end + return '' end @@ -130,29 +130,29 @@ end -- @param bdata Data to be encoded. -- @return Base64-encoded string. function enc(bdata) - local pos = 1 - local byte - local nbyte = '' - -- local nbuffer = {} - local b64dataBuf = {} - while pos <= #bdata do - pos, byte = bunpack("B1", bdata, pos) - nbyte = nbyte .. byte - append(b64dataBuf, b64enc6bit(substr(nbyte, 1, 6))) - nbyte = substr(nbyte,7) - if (#nbyte == 6) then - append(b64dataBuf, b64enc6bit(nbyte)) - nbyte = '' - end - end - if #nbyte == 2 then - append(b64dataBuf, b64enc6bit(nbyte .. "0000") ) - append(b64dataBuf, "==") - elseif #nbyte == 4 then - append(b64dataBuf, b64enc6bit(nbyte .. "00")) - append(b64dataBuf, '=') - end - return concat(b64dataBuf) + local pos = 1 + local byte + local nbyte = '' + -- local nbuffer = {} + local b64dataBuf = {} + while pos <= #bdata do + pos, byte = bunpack("B1", bdata, pos) + nbyte = nbyte .. byte + append(b64dataBuf, b64enc6bit(substr(nbyte, 1, 6))) + nbyte = substr(nbyte,7) + if (#nbyte == 6) then + append(b64dataBuf, b64enc6bit(nbyte)) + nbyte = '' + end + end + if #nbyte == 2 then + append(b64dataBuf, b64enc6bit(nbyte .. "0000") ) + append(b64dataBuf, "==") + elseif #nbyte == 4 then + append(b64dataBuf, b64enc6bit(nbyte .. "00")) + append(b64dataBuf, '=') + end + return concat(b64dataBuf) end @@ -161,21 +161,21 @@ end -- @param b64data Base64 encoded data. -- @return Decoded data. function dec(b64data) - local bdataBuf = {} - local pos = 1 - local byte - local nbyte = '' - for pos = 1, #b64data do -- while pos <= #b64data do - byte = b64dec6bit(substr(b64data, pos, pos)) - if not byte then return end - nbyte = nbyte .. byte - if #nbyte >= 8 then - append(bdataBuf, bpack("B", substr(nbyte, 1, 8))) - nbyte = substr(nbyte, 9) - end --- pos = pos + 1 - end - return concat(bdataBuf) + local bdataBuf = {} + local pos = 1 + local byte + local nbyte = '' + for pos = 1, #b64data do -- while pos <= #b64data do + byte = b64dec6bit(substr(b64data, pos, pos)) + if not byte then return end + nbyte = nbyte .. byte + if #nbyte >= 8 then + append(bdataBuf, bpack("B", substr(nbyte, 1, 8))) + nbyte = substr(nbyte, 9) + end + -- pos = pos + 1 + end + return concat(bdataBuf) end diff --git a/nselib/bjnp.lua b/nselib/bjnp.lua index 5f5b43a5e..f9a0ac2a8 100644 --- a/nselib/bjnp.lua +++ b/nselib/bjnp.lua @@ -20,343 +20,343 @@ _ENV = stdnse.module("bjnp", stdnse.seeall) BJNP = { - -- The common BJNP header - Header = { + -- The common BJNP header + Header = { - new = function(self, o) - o = o or {} - o = { - id = o.id or "BJNP", - type = o.type or 1, - code = o.code, - seq = o.seq or 1, - session = o.session or 0, - length = o.length or 0, - } - assert(o.code, "code argument required") - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, o) + o = o or {} + o = { + id = o.id or "BJNP", + type = o.type or 1, + code = o.code, + seq = o.seq or 1, + session = o.session or 0, + length = o.length or 0, + } + assert(o.code, "code argument required") + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local hdr = BJNP.Header:new({ code = -1 }) - local pos + parse = function(data) + local hdr = BJNP.Header:new({ code = -1 }) + local pos - pos, hdr.id, hdr.type, hdr.code, - hdr.seq, hdr.session, hdr.length = bin.unpack(">A4CCISI", data) - return hdr - end, + pos, hdr.id, hdr.type, hdr.code, + hdr.seq, hdr.session, hdr.length = bin.unpack(">A4CCISI", data) + return hdr + end, - __tostring = function(self) - return bin.pack(">ACCISI", - self.id, - self.type, - self.code, - self.seq, - self.session, - self.length - ) - end - }, + __tostring = function(self) + return bin.pack(">ACCISI", + self.id, + self.type, + self.code, + self.seq, + self.session, + self.length + ) + end + }, - -- Scanner related code - Scanner = { + -- Scanner related code + Scanner = { - Code = { - DISCOVER = 1, - IDENTITY = 48, - }, + Code = { + DISCOVER = 1, + IDENTITY = 48, + }, - Request = { + Request = { - Discover = { + Discover = { - new = function(self) - local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.DISCOVER }) } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.DISCOVER }) } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return tostring(self.header) - end, - }, + __tostring = function(self) + return tostring(self.header) + end, + }, - Identity = { + Identity = { - new = function(self) - local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.IDENTITY, length = 4 }), data = 0 } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { header = BJNP.Header:new( { type = 2, code = BJNP.Scanner.Code.IDENTITY, length = 4 }), data = 0 } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return tostring(self.header) .. bin.pack(">I", self.data) - end, - } + __tostring = function(self) + return tostring(self.header) .. bin.pack(">I", self.data) + end, + } - }, + }, - Response = { + Response = { - Identity = { + Identity = { - new = function(self) - local o = {} - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = {} + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local identity = BJNP.Scanner.Response.Identity:new() - identity.header = BJNP.Header.parse(data) + parse = function(data) + local identity = BJNP.Scanner.Response.Identity:new() + identity.header = BJNP.Header.parse(data) - local pos = #tostring(identity.header) + 1 - local pos, len = bin.unpack(">S", data, pos) - if ( len ) then - pos, identity.data = bin.unpack("A" .. len - 2, data, pos) - return identity - end - end, + local pos = #tostring(identity.header) + 1 + local pos, len = bin.unpack(">S", data, pos) + if ( len ) then + pos, identity.data = bin.unpack("A" .. len - 2, data, pos) + return identity + end + end, - } + } - } + } - }, + }, - -- Printer related code - Printer = { + -- Printer related code + Printer = { - Code = { - DISCOVER = 1, - IDENTITY = 48, - }, + Code = { + DISCOVER = 1, + IDENTITY = 48, + }, - Request = { + Request = { - Discover = { - new = function(self) - local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.DISCOVER }) } - setmetatable(o, self) - self.__index = self - return o - end, + Discover = { + new = function(self) + local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.DISCOVER }) } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return tostring(self.header) - end, - }, + __tostring = function(self) + return tostring(self.header) + end, + }, - Identity = { + Identity = { - new = function(self) - local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.IDENTITY }) } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { header = BJNP.Header:new( { code = BJNP.Printer.Code.IDENTITY }) } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return tostring(self.header) - end, - } + __tostring = function(self) + return tostring(self.header) + end, + } - }, + }, - Response = { + Response = { - Identity = { + Identity = { - new = function(self) - local o = {} - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = {} + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local identity = BJNP.Printer.Response.Identity:new() - identity.header = BJNP.Header.parse(data) + parse = function(data) + local identity = BJNP.Printer.Response.Identity:new() + identity.header = BJNP.Header.parse(data) - local pos = #tostring(identity.header) + 1 - local pos, len = bin.unpack(">S", data, pos) - if ( len ) then - pos, identity.data = bin.unpack("A" .. len - 2, data, pos) - return identity - end - end, + local pos = #tostring(identity.header) + 1 + local pos, len = bin.unpack(">S", data, pos) + if ( len ) then + pos, identity.data = bin.unpack("A" .. len - 2, data, pos) + return identity + end + end, - } + } - }, + }, - } + } } -- Helper class, the main script writer interface Helper = { - -- Creates a new Helper instance - -- @param host table - -- @param port table - -- @param options table containing one or more of the following fields; - -- timeout - the timeout in milliseconds for socket communication - -- bcast - instructs the library that the host is a broadcast - -- address - -- @return o new instance of Helper - new = function(self, host, port, options) - local o = { - host = host, port = port, options = options or {} - } - o.options.timeout = o.options.timeout or 5000 - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Helper instance + -- @param host table + -- @param port table + -- @param options table containing one or more of the following fields; + -- timeout - the timeout in milliseconds for socket communication + -- bcast - instructs the library that the host is a broadcast + -- address + -- @return o new instance of Helper + new = function(self, host, port, options) + local o = { + host = host, port = port, options = options or {} + } + o.options.timeout = o.options.timeout or 5000 + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects the socket to the device - -- This should always be called, regardless if the broadcast option is set - -- or not. - -- - -- @return status, true on success, false on failure - -- @return err string containing the error message if status is false - connect = function(self) - self.socket = nmap.new_socket(( self.options.bcast and "udp" )) - self.socket:set_timeout(self.options.timeout) - if ( not(self.options.bcast) ) then - return self.socket:connect(self.host, self.port) - end - return true - end, + -- Connects the socket to the device + -- This should always be called, regardless if the broadcast option is set + -- or not. + -- + -- @return status, true on success, false on failure + -- @return err string containing the error message if status is false + connect = function(self) + self.socket = nmap.new_socket(( self.options.bcast and "udp" )) + self.socket:set_timeout(self.options.timeout) + if ( not(self.options.bcast) ) then + return self.socket:connect(self.host, self.port) + end + return true + end, - -- Discover network devices using either broadcast or unicast - -- @param packet discovery packet (printer or scanner) - -- @return status, true on success, false on failure - -- @return devices table containing discovered devices when status is true - -- errmsg string containing the error message when status is false - discoverDevice = function(self, packet) - if ( not(self.options.bcast) ) then - if ( not(self.socket:send(tostring(packet))) ) then - return false, "Failed to send request to server" - end - else - if ( not(self.socket:sendto(self.host, self.port, tostring(packet))) ) then - return false, "Failed to send request to server" - end - end - -- discover run in loop - local devices, tmp = {}, {} - local start = os.time() - while( true ) do - local status, data = self.socket:receive() - if ( not(status) or ( os.time() - start > ( self.options.timeout/1000 - 1 ) )) then - break - end - local status, _, _, rhost = self.socket:get_info() - tmp[rhost] = true - end - for host in pairs(tmp) do table.insert(devices, host) end - return true, ( self.options.bcast and devices or ( #devices > 0 and devices[1] )) - end, + -- Discover network devices using either broadcast or unicast + -- @param packet discovery packet (printer or scanner) + -- @return status, true on success, false on failure + -- @return devices table containing discovered devices when status is true + -- errmsg string containing the error message when status is false + discoverDevice = function(self, packet) + if ( not(self.options.bcast) ) then + if ( not(self.socket:send(tostring(packet))) ) then + return false, "Failed to send request to server" + end + else + if ( not(self.socket:sendto(self.host, self.port, tostring(packet))) ) then + return false, "Failed to send request to server" + end + end + -- discover run in loop + local devices, tmp = {}, {} + local start = os.time() + while( true ) do + local status, data = self.socket:receive() + if ( not(status) or ( os.time() - start > ( self.options.timeout/1000 - 1 ) )) then + break + end + local status, _, _, rhost = self.socket:get_info() + tmp[rhost] = true + end + for host in pairs(tmp) do table.insert(devices, host) end + return true, ( self.options.bcast and devices or ( #devices > 0 and devices[1] )) + end, - -- Discover BJNP supporting scanners - discoverScanner = function(self) - return self:discoverDevice(BJNP.Scanner.Request.Discover:new()) - end, + -- Discover BJNP supporting scanners + discoverScanner = function(self) + return self:discoverDevice(BJNP.Scanner.Request.Discover:new()) + end, - -- Discover BJNP supporting printers - discoverPrinter = function(self) - return self:discoverDevice(BJNP.Printer.Request.Discover:new()) - end, + -- Discover BJNP supporting printers + discoverPrinter = function(self) + return self:discoverDevice(BJNP.Printer.Request.Discover:new()) + end, - -- Gets a printer identity (additional information) - -- @param devtype string containing either the string printer or scanner - -- @return status, true on success, false on failure - -- @return attribs table containing device attributes when status is true - -- errmsg string containing the error message when status is false - getDeviceIdentity = function(self, devtype) - -- Were currenlty only decoding this as I don't know what the other cruft is - local attrib_names = { - ["scanner"] = { - { ['MFG'] = "Manufacturer" }, - { ['MDL'] = "Model" }, - { ['DES'] = "Description" }, - { ['CMD'] = "Command" }, - }, - ["printer"] = { - { ['MFG'] = "Manufacturer" }, - { ['MDL'] = "Model" }, - { ['DES'] = "Description" }, - { ['VER'] = "Firmware version" }, - { ['CMD'] = "Command" }, - } - } - local identity - if ( "printer" == devtype ) then - identity = BJNP.Printer.Request.Identity:new() - elseif ( "scanner" == devtype ) then - identity = BJNP.Scanner.Request.Identity:new() - end - assert(not(self.options.bcast), "getIdentity is not supported for broadcast") - if ( not(self.socket:send(tostring(identity))) ) then - return false, "Failed to send request to server" - end - local status, data = self.socket:receive() - if ( not(status) ) then - return false, "Failed to receive response from server" - end + -- Gets a printer identity (additional information) + -- @param devtype string containing either the string printer or scanner + -- @return status, true on success, false on failure + -- @return attribs table containing device attributes when status is true + -- errmsg string containing the error message when status is false + getDeviceIdentity = function(self, devtype) + -- Were currenlty only decoding this as I don't know what the other cruft is + local attrib_names = { + ["scanner"] = { + { ['MFG'] = "Manufacturer" }, + { ['MDL'] = "Model" }, + { ['DES'] = "Description" }, + { ['CMD'] = "Command" }, + }, + ["printer"] = { + { ['MFG'] = "Manufacturer" }, + { ['MDL'] = "Model" }, + { ['DES'] = "Description" }, + { ['VER'] = "Firmware version" }, + { ['CMD'] = "Command" }, + } + } + local identity + if ( "printer" == devtype ) then + identity = BJNP.Printer.Request.Identity:new() + elseif ( "scanner" == devtype ) then + identity = BJNP.Scanner.Request.Identity:new() + end + assert(not(self.options.bcast), "getIdentity is not supported for broadcast") + if ( not(self.socket:send(tostring(identity))) ) then + return false, "Failed to send request to server" + end + local status, data = self.socket:receive() + if ( not(status) ) then + return false, "Failed to receive response from server" + end - local identity - if ( "printer" == devtype ) then - identity = BJNP.Printer.Response.Identity.parse(data) - elseif ( "scanner" == devtype ) then - identity = BJNP.Scanner.Response.Identity.parse(data) - end - if ( not(identity) ) then - return false, "Failed to parse identity" - end - local attrs, kvps = {}, {} + local identity + if ( "printer" == devtype ) then + identity = BJNP.Printer.Response.Identity.parse(data) + elseif ( "scanner" == devtype ) then + identity = BJNP.Scanner.Response.Identity.parse(data) + end + if ( not(identity) ) then + return false, "Failed to parse identity" + end + local attrs, kvps = {}, {} - for k, v in ipairs(stdnse.strsplit(";", identity.data)) do - local nm, val = v:match("^([^:]*):(.*)$") - if ( nm ) then kvps[nm] = val end - end + for k, v in ipairs(stdnse.strsplit(";", identity.data)) do + local nm, val = v:match("^([^:]*):(.*)$") + if ( nm ) then kvps[nm] = val end + end - for _, attrib in ipairs(attrib_names[devtype]) do - local short, long = next(attrib) - if ( kvps[short] ) then - table.insert(attrs, ("%s: %s"):format(long, kvps[short])) - end - end + for _, attrib in ipairs(attrib_names[devtype]) do + local short, long = next(attrib) + if ( kvps[short] ) then + table.insert(attrs, ("%s: %s"):format(long, kvps[short])) + end + end - return true, attrs - end, + return true, attrs + end, - -- Retrieves information related to the printer - getPrinterIdentity = function(self) - return self:getDeviceIdentity("printer") - end, + -- Retrieves information related to the printer + getPrinterIdentity = function(self) + return self:getDeviceIdentity("printer") + end, - -- Retrieves information related to the scanner - getScannerIdentity = function(self) - return self:getDeviceIdentity("scanner") - end, + -- Retrieves information related to the scanner + getScannerIdentity = function(self) + return self:getDeviceIdentity("scanner") + end, - -- Closes the connection - -- @return status, true on success, false on failure - -- @return errmsg string containing the error message when status is false - close = function(self) - return self.socket:close() - end + -- Closes the connection + -- @return status, true on success, false on failure + -- @return errmsg string containing the error message when status is false + close = function(self) + return self.socket:close() + end } diff --git a/nselib/citrixxml.lua b/nselib/citrixxml.lua index c559b47a2..8d9c2bb57 100644 --- a/nselib/citrixxml.lua +++ b/nselib/citrixxml.lua @@ -30,23 +30,23 @@ _ENV = stdnse.module("citrixxml", stdnse.seeall) -- @return string an e function decode_xml_document(xmldata) - local hexval + local hexval - if not xmldata then - return "" - end + if not xmldata then + return "" + end - local newstr = xmldata + local newstr = xmldata - for m in xmldata:gmatch("(&#%d+;)") do - hexval = m:match("(%d+)") + for m in xmldata:gmatch("(&#%d+;)") do + hexval = m:match("(%d+)") - if ( hexval ) then - newstr = xmldata:gsub(m, string.char(hexval)) - end - end + if ( hexval ) then + newstr = xmldata:gsub(m, string.char(hexval)) + end + end - return newstr + return newstr end @@ -60,12 +60,12 @@ end -- function send_citrix_xml_request(host, port, xmldata) - local response = http.post( host, port, "/scripts/WPnBr.dll", { header={["Content-Type"]="text/xml"}}, nil, xmldata) + local response = http.post( host, port, "/scripts/WPnBr.dll", { header={["Content-Type"]="text/xml"}}, nil, xmldata) - -- this is *probably* not the right way to do stuff - -- decoding should *probably* only be done on XML-values - -- this is *probably* defined in the standard, for anyone interested - return decode_xml_document(response.body) + -- this is *probably* not the right way to do stuff + -- decoding should *probably* only be done on XML-values + -- this is *probably* defined in the standard, for anyone interested + return decode_xml_document(response.body) end @@ -81,13 +81,13 @@ end -- function request_server_farm_data( host, port ) - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end --- Parses the response from the request_server_farm_data request @@ -96,14 +96,14 @@ end -- function parse_server_farm_data_response( response ) - local farms = {} + local farms = {} - response = response:gsub("\r?\n","") - for farm in response:gmatch("([^<]+)") do - table.insert(farms, farm) - end + response = response:gsub("\r?\n","") + for farm in response:gmatch("([^<]+)") do + table.insert(farms, farm) + end - return farms + return farms end @@ -121,37 +121,37 @@ end -- function request_appdata(host, port, params) - -- setup the mandatory parameters if they're missing - local scope = params['Scope'] or "onelevel" - local server_type = params['ServerType'] or "all" - local client_type = params['ClientType'] or "ica30" - local desired_details = params['DesiredDetails'] or nil + -- setup the mandatory parameters if they're missing + local scope = params['Scope'] or "onelevel" + local server_type = params['ServerType'] or "all" + local client_type = params['ClientType'] or "ica30" + local desired_details = params['DesiredDetails'] or nil - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "" .. server_type .. "" - xmldata = xmldata .. "" .. client_type .. "" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "" .. server_type .. "" + xmldata = xmldata .. "" .. client_type .. "" - if desired_details then - if type(desired_details) == "string" then - xmldata = xmldata .. "" .. desired_details .. "" - elseif type(desired_details) == "table" then - for _, v in ipairs(desired_details) do - xmldata = xmldata .. "" .. v .. "" - end - else - assert(desired_details) - end + if desired_details then + if type(desired_details) == "string" then + xmldata = xmldata .. "" .. desired_details .. "" + elseif type(desired_details) == "table" then + for _, v in ipairs(desired_details) do + xmldata = xmldata .. "" .. v .. "" + end + else + assert(desired_details) + end - end + end - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end @@ -161,56 +161,56 @@ end -- @return table containing settings extracted from the accesslist section of the response local function extract_appdata_acls(xmldata) - local acls = {} - local users = {} - local groups = {} + local acls = {} + local users = {} + local groups = {} - for acl in xmldata:gmatch("(.-)") do + for acl in xmldata:gmatch("(.-)") do - if acl:match("AnonymousUser") then - table.insert(users, "Anonymous") - else + if acl:match("AnonymousUser") then + table.insert(users, "Anonymous") + else - for user in acl:gmatch("(.-)") do - local user_name = user:match("(.-)") or "" - local domain_name = user:match("(.-)") or "" + for user in acl:gmatch("(.-)") do + local user_name = user:match("(.-)") or "" + local domain_name = user:match("(.-)") or "" - if user_name:len() > 0 then - if domain_name:len() > 0 then - domain_name = domain_name .. "\\" - end - table.insert(users, domain_name .. user_name) - end + if user_name:len() > 0 then + if domain_name:len() > 0 then + domain_name = domain_name .. "\\" + end + table.insert(users, domain_name .. user_name) + end - end + end - for group in acl:gmatch("(.-)") do + for group in acl:gmatch("(.-)") do - local group_name = group:match("(.-)") or "" - local domain_name = group:match("(.-)") or "" + local group_name = group:match("(.-)") or "" + local domain_name = group:match("(.-)") or "" - if group_name:len() > 0 then - if domain_name:len() > 0 then - domain_name = domain_name .. "\\" - end - table.insert(groups, domain_name .. group_name) - end + if group_name:len() > 0 then + if domain_name:len() > 0 then + domain_name = domain_name .. "\\" + end + table.insert(groups, domain_name .. group_name) + end - end + end - end + end - if #users> 0 then - acls['User'] = users - end - if #groups>0 then - acls['Group'] = groups - end + if #users> 0 then + acls['User'] = users + end + if #groups>0 then + acls['Group'] = groups + end - end + end - return acls + return acls end @@ -221,21 +221,21 @@ end -- @return table containing settings extracted from the settings section of the response local function extract_appdata_settings(xmldata) - local settings = {} + local settings = {} - settings['appisdisabled'] = xmldata:match("") - settings['appisdesktop'] = xmldata:match("") + settings['appisdisabled'] = xmldata:match("") + settings['appisdesktop'] = xmldata:match("") - for s in xmldata:gmatch("(.-)") do - settings['Encryption'] = s:match("(.-)") - settings['AppOnDesktop'] = s:match("") - settings['AppInStartmenu'] = s:match("") - settings['PublisherName'] = s:match("(.-)") - settings['SSLEnabled'] = s:match("(.-)") - settings['RemoteAccessEnabled'] = s:match("(.-)") - end + for s in xmldata:gmatch("(.-)") do + settings['Encryption'] = s:match("(.-)") + settings['AppOnDesktop'] = s:match("") + settings['AppInStartmenu'] = s:match("") + settings['PublisherName'] = s:match("(.-)") + settings['SSLEnabled'] = s:match("(.-)") + settings['RemoteAccessEnabled'] = s:match("(.-)") + end - return settings + return settings end @@ -245,23 +245,23 @@ end -- @return table containing nestled tables closely resembling the DOM model of the XML response function parse_appdata_response(xmldata) - local apps = {} - xmldata = xmldata:gsub("\r?\n",""):gsub(">%s+<", "><") + local apps = {} + xmldata = xmldata:gsub("\r?\n",""):gsub(">%s+<", "><") - for AppData in xmldata:gmatch("(.-)") do + for AppData in xmldata:gmatch("(.-)") do - local app_name = AppData:match("(.-)") or "" - local app = {} + local app_name = AppData:match("(.-)") or "" + local app = {} - app['FName'] = app_name - app['AccessList'] = extract_appdata_acls(AppData) - app['Settings'] = extract_appdata_settings(AppData) + app['FName'] = app_name + app['AccessList'] = extract_appdata_acls(AppData) + app['Settings'] = extract_appdata_settings(AppData) - table.insert(apps, app) + table.insert(apps, app) - end + end - return apps + return apps end -- @@ -270,25 +270,25 @@ end -- function request_address(host, port, flags, appname) - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" - if flags then - xmldata = xmldata .. "" .. flags .. "" - end + if flags then + xmldata = xmldata .. "" .. flags .. "" + end - if appname then - xmldata = xmldata .. "" - xmldata = xmldata .. "" .. appname .. "" - xmldata = xmldata .. "" - end + if appname then + xmldata = xmldata .. "" + xmldata = xmldata .. "" .. appname .. "" + xmldata = xmldata .. "" + end - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end --- Request information about the Citrix protocol @@ -304,27 +304,27 @@ end -- function request_server_data(host, port, params) - local params = params or {} - local server_type = params.ServerType or {"all"} - local client_type = params.ClientType or {"all"} + local params = params or {} + local server_type = params.ServerType or {"all"} + local client_type = params.ClientType or {"all"} - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" - for _, srvtype in pairs(server_type) do - xmldata = xmldata .. "" .. srvtype .. "" - end + for _, srvtype in pairs(server_type) do + xmldata = xmldata .. "" .. srvtype .. "" + end - for _, clitype in pairs(client_type) do - xmldata = xmldata .. "" .. clitype .. "" - end + for _, clitype in pairs(client_type) do + xmldata = xmldata .. "" .. clitype .. "" + end - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end --- Parses the response from the request_server_data request @@ -333,14 +333,14 @@ end -- function parse_server_data_response(response) - local servers = {} + local servers = {} - response = response:gsub("\r?\n","") - for s in response:gmatch("([^<]+)") do - table.insert(servers, s) - end + response = response:gsub("\r?\n","") + for s in response:gmatch("([^<]+)") do + table.insert(servers, s) + end - return servers + return servers end @@ -357,22 +357,22 @@ end -- function request_protocol_info( host, port, params ) - local params = params or {} + local params = params or {} - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" - if params['ServerAddress'] then - xmldata = xmldata .. "" - xmldata = xmldata .. params['ServerAddress'] .. "" - end + if params['ServerAddress'] then + xmldata = xmldata .. "" + xmldata = xmldata .. params['ServerAddress'] .. "" + end - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end --- Request capability information @@ -387,14 +387,14 @@ end -- function request_capabilities( host, port ) - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end --- Parses the response from the request_capabilities request @@ -403,14 +403,14 @@ end -- function parse_capabilities_response(response) - local servers = {} + local servers = {} - response = response:gsub("\r?\n","") - for s in response:gmatch("([^<]+)") do - table.insert(servers, s) - end + response = response:gsub("\r?\n","") + for s in response:gmatch("([^<]+)") do + table.insert(servers, s) + end - return servers + return servers end @@ -428,32 +428,32 @@ end -- function request_validate_credentials(host, port, params ) - local params = params or {} - local credentials = params['Credentials'] or {} + local params = params or {} + local credentials = params['Credentials'] or {} - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "" - if credentials['UserName'] then - xmldata = xmldata .. "" .. credentials['UserName'] .. "" - end + if credentials['UserName'] then + xmldata = xmldata .. "" .. credentials['UserName'] .. "" + end - if credentials['Password'] then - xmldata = xmldata .. "" .. credentials['Password'] .. "" - end + if credentials['Password'] then + xmldata = xmldata .. "" .. credentials['Password'] .. "" + end - if credentials['Domain'] then - xmldata = xmldata .. "" .. credentials['Domain'] .. "" - end + if credentials['Domain'] then + xmldata = xmldata .. "" .. credentials['Domain'] .. "" + end - xmldata = xmldata .. "" - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end @@ -463,14 +463,14 @@ end -- @return table containing the results -- function parse_validate_credentials_response(response) - local tblResult = {} + local tblResult = {} - response = response:gsub("\r?\n","") - tblResult['DaysUntilPasswordExpiry'] = response:match("(.+)") - tblResult['ShowPasswordExpiryWarning'] = response:match("(.+)") - tblResult['ErrorId'] = response:match("(.+)") + response = response:gsub("\r?\n","") + tblResult['DaysUntilPasswordExpiry'] = response:match("(.+)") + tblResult['ShowPasswordExpiryWarning'] = response:match("(.+)") + tblResult['ErrorId'] = response:match("(.+)") - return tblResult + return tblResult end @@ -485,53 +485,53 @@ end -- function request_reconnect_session_data(host, port, params) - local params = params or {} - local Credentials = params.Credentials or {} + local params = params or {} + local Credentials = params.Credentials or {} - params.ServerType = params.ServerType or {} - params.ClientType = params.ClientType or {} + params.ServerType = params.ServerType or {} + params.ClientType = params.ClientType or {} - local xmldata = "\r\n" - xmldata = xmldata .. "\r\n" - xmldata = xmldata .. "" - xmldata = xmldata .. "" + local xmldata = "\r\n" + xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "" - xmldata = xmldata .. "" + xmldata = xmldata .. "" - if Credentials.UserName then - xmldata = xmldata .. "" .. Credentials.UserName .. "" - end + if Credentials.UserName then + xmldata = xmldata .. "" .. Credentials.UserName .. "" + end - if Credentials.Password then - xmldata = xmldata .. "" .. Credentials.Password .. "" - end + if Credentials.Password then + xmldata = xmldata .. "" .. Credentials.Password .. "" + end - if Credentials.Domain then - xmldata = xmldata .. "" .. Credentials.Domain .. "" - end + if Credentials.Domain then + xmldata = xmldata .. "" .. Credentials.Domain .. "" + end - xmldata = xmldata .. "" + xmldata = xmldata .. "" - if params.ClientName then - xmldata = xmldata .. "" .. params.ClientName .. "" - end + if params.ClientName then + xmldata = xmldata .. "" .. params.ClientName .. "" + end - if params.DeviceId then - xmldata = xmldata .. "" .. params.DeviceId .. "" - end + if params.DeviceId then + xmldata = xmldata .. "" .. params.DeviceId .. "" + end - for _, srvtype in pairs(params.ServerType) do - xmldata = xmldata .. "" .. srvtype .. "" - end + for _, srvtype in pairs(params.ServerType) do + xmldata = xmldata .. "" .. srvtype .. "" + end - for _, clitype in pairs(params.ClientType) do - xmldata = xmldata .. "" .. clitype .. "" - end + for _, clitype in pairs(params.ClientType) do + xmldata = xmldata .. "" .. clitype .. "" + end - xmldata = xmldata .. "" - xmldata = xmldata .. "\r\n" + xmldata = xmldata .. "" + xmldata = xmldata .. "\r\n" - return send_citrix_xml_request(host, port, xmldata) + return send_citrix_xml_request(host, port, xmldata) end diff --git a/nselib/cvs.lua b/nselib/cvs.lua index 6a338c93a..6ca28c7d6 100644 --- a/nselib/cvs.lua +++ b/nselib/cvs.lua @@ -18,80 +18,80 @@ _ENV = stdnse.module("cvs", stdnse.seeall) Helper = { - new = function(self, host, port) - local o = { host = host, port = port } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, host, port) + local o = { host = host, port = port } + setmetatable(o, self) + self.__index = self + return o + end, - connect = function(self) - self.socket = nmap.new_socket() - return self.socket:connect(self.host, self.port) - end, + connect = function(self) + self.socket = nmap.new_socket() + return self.socket:connect(self.host, self.port) + end, - login = function(self, repo, user, pass ) - local auth_tab = {} - assert(repo, "No repository was specified") - assert(user, "No user was specified") - assert(pass, "No pass was specified") + login = function(self, repo, user, pass ) + local auth_tab = {} + assert(repo, "No repository was specified") + assert(user, "No user was specified") + assert(pass, "No pass was specified") - -- Add a leading slash if it's missing - if ( repo:sub(1,1) ~= "/" ) then repo = "/" .. repo end + -- Add a leading slash if it's missing + if ( repo:sub(1,1) ~= "/" ) then repo = "/" .. repo end - table.insert(auth_tab, "BEGIN AUTH REQUEST") - table.insert(auth_tab, repo) - table.insert(auth_tab, user) - table.insert(auth_tab, Util.pwscramble(pass)) - table.insert(auth_tab, "END AUTH REQUEST") + table.insert(auth_tab, "BEGIN AUTH REQUEST") + table.insert(auth_tab, repo) + table.insert(auth_tab, user) + table.insert(auth_tab, Util.pwscramble(pass)) + table.insert(auth_tab, "END AUTH REQUEST") - local data = stdnse.strjoin("\n", auth_tab) .. "\n" - local status = self.socket:send(data) - if ( not(status) ) then return false, "Failed to send login request" end + local data = stdnse.strjoin("\n", auth_tab) .. "\n" + local status = self.socket:send(data) + if ( not(status) ) then return false, "Failed to send login request" end - local status, response = self.socket:receive() - if ( not(status) ) then return false, "Failed to read login response" end + local status, response = self.socket:receive() + if ( not(status) ) then return false, "Failed to read login response" end - if ( response == "I LOVE YOU\n" ) then return true end - return false, response - end, + if ( response == "I LOVE YOU\n" ) then return true end + return false, response + end, - close = function(self) - return self.socket:close() - end + close = function(self) + return self.socket:close() + end } Util = { - --- Scrambles a password - -- - -- @param password string containing the password to scramble - pwscramble = function(password) - local shifts = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, - 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, - 114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87, - 111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105, - 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35, - 125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56, - 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, - 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223, - 225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190, - 199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193, - 174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212, - 207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246, - 192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176, - 227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127, - 182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195, - 243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152 }; + --- Scrambles a password + -- + -- @param password string containing the password to scramble + pwscramble = function(password) + local shifts = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 114,120, 53, 79, 96,109, 72,108, 70, 64, 76, 67,116, 74, 68, 87, + 111, 52, 75,119, 49, 34, 82, 81, 95, 65,112, 86,118,110,122,105, + 41, 57, 83, 43, 46,102, 40, 89, 38,103, 45, 50, 42,123, 91, 35, + 125, 55, 54, 66,124,126, 59, 47, 92, 71,115, 78, 88,107,106, 56, + 36,121,117,104,101,100, 69, 73, 99, 63, 94, 93, 39, 37, 61, 48, + 58,113, 32, 90, 44, 98, 60, 51, 33, 97, 62, 77, 84, 80, 85,223, + 225,216,187,166,229,189,222,188,141,249,148,200,184,136,248,190, + 199,170,181,204,138,232,218,183,255,234,220,247,213,203,226,193, + 174,172,228,252,217,201,131,230,197,211,145,238,161,179,160,212, + 207,221,254,173,202,146,224,151,140,196,205,130,135,133,143,246, + 192,159,244,239,185,168,215,144,139,165,180,157,147,186,214,176, + 227,231,219,169,175,156,206,198,129,164,150,210,154,177,134,127, + 182,128,158,208,162,132,167,209,149,241,153,251,237,236,171,195, + 243,233,253,240,194,250,191,155,142,137,245,235,163,242,178,152 }; - local result = "" - for i = 1, #password do - result = result .. string.char(shifts[password:byte(i)+1]) - end - return 'A' .. result - end + local result = "" + for i = 1, #password do + result = result .. string.char(shifts[password:byte(i)+1]) + end + return 'A' .. result + end } diff --git a/nselib/datafiles.lua b/nselib/datafiles.lua index 66793b209..d4133ee8a 100644 --- a/nselib/datafiles.lua +++ b/nselib/datafiles.lua @@ -24,12 +24,12 @@ _ENV = stdnse.module("datafiles", stdnse.seeall) -- @name common_files -- @see parse_file local common_files = { - ["nmap-rpc"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)" ) ) end] = "^%s*([^%s#]+)%s+%d+" }, - ["nmap-protocols"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)" ) ) end] = "^%s*([^%s#]+)%s+%d+" }, - ["nmap-services"] = { ["tcp"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)/tcp" ) ) end] = "^%s*([^%s#]+)%s+%d+/tcp" }, - ["udp"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)/udp" ) ) end] = "^%s*([^%s#]+)%s+%d+/udp" } - }, - ["nmap-mac-prefixes"] = { [ "^%s*(%w+)%s+[^#]+" ] = "^%s*%w+%s+([^#]+)" } + ["nmap-rpc"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)" ) ) end] = "^%s*([^%s#]+)%s+%d+" }, + ["nmap-protocols"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)" ) ) end] = "^%s*([^%s#]+)%s+%d+" }, + ["nmap-services"] = { ["tcp"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)/tcp" ) ) end] = "^%s*([^%s#]+)%s+%d+/tcp" }, + ["udp"] = { [function(ln) return tonumber( ln:match( "^%s*[^%s#]+%s+(%d+)/udp" ) ) end] = "^%s*([^%s#]+)%s+%d+/udp" } + }, + ["nmap-mac-prefixes"] = { [ "^%s*(%w+)%s+[^#]+" ] = "^%s*%w+%s+([^#]+)" } } diff --git a/nselib/dnssd.lua b/nselib/dnssd.lua index 873afbaf5..3376b5a5e 100644 --- a/nselib/dnssd.lua +++ b/nselib/dnssd.lua @@ -49,347 +49,347 @@ _ENV = stdnse.module("dnssd", stdnse.seeall) Util = { - --- Compare function used for sorting IP-addresses - -- - -- @param a table containing first item - -- @param b table containing second item - -- @return true if a is less than b - ipCompare = function(a, b) - return ipOps.compare_ip(a, "lt", b) - end, + --- Compare function used for sorting IP-addresses + -- + -- @param a table containing first item + -- @param b table containing second item + -- @return true if a is less than b + ipCompare = function(a, b) + return ipOps.compare_ip(a, "lt", b) + end, - --- Function used to compare discovered DNS services so they can be sorted - -- - -- @param a table containing first item - -- @param b table containing second item - -- @return true if the port of a is less than the port of b - serviceCompare = function(a, b) - -- if no port is found use 999999 for comparing, this way all services - -- without ports and device information gets printed at the end - local port_a = a.name:match("^(%d+)") or 999999 - local port_b = b.name:match("^(%d+)") or 999999 + --- Function used to compare discovered DNS services so they can be sorted + -- + -- @param a table containing first item + -- @param b table containing second item + -- @return true if the port of a is less than the port of b + serviceCompare = function(a, b) + -- if no port is found use 999999 for comparing, this way all services + -- without ports and device information gets printed at the end + local port_a = a.name:match("^(%d+)") or 999999 + local port_b = b.name:match("^(%d+)") or 999999 - if ( tonumber(port_a) < tonumber(port_b) ) then - return true - end - return false - end, + if ( tonumber(port_a) < tonumber(port_b) ) then + return true + end + return false + end, - --- Creates a service host table - -- - -- ['_ftp._tcp.local'] = {10.10.10.10,20.20.20.20} - -- ['_http._tcp.local'] = {30.30.30.30,40.40.40.40} - -- - -- @param response containing multiple responses from dns.query - -- @return services table containing the service name as a key and all host addresses as value - createSvcHostTbl = function( response ) - local services = {} - -- Create unique table of services - for _, r in ipairs( response ) do - -- do we really have mutliple responses? - if ( not(r.output) ) then return end - for _, svc in ipairs(r.output ) do - services[svc] = services[svc] or {} - table.insert(services[svc], r.peer) - end - end + --- Creates a service host table + -- + -- ['_ftp._tcp.local'] = {10.10.10.10,20.20.20.20} + -- ['_http._tcp.local'] = {30.30.30.30,40.40.40.40} + -- + -- @param response containing multiple responses from dns.query + -- @return services table containing the service name as a key and all host addresses as value + createSvcHostTbl = function( response ) + local services = {} + -- Create unique table of services + for _, r in ipairs( response ) do + -- do we really have mutliple responses? + if ( not(r.output) ) then return end + for _, svc in ipairs(r.output ) do + services[svc] = services[svc] or {} + table.insert(services[svc], r.peer) + end + end - return services - end, + return services + end, - --- Creates a unique list of services - -- - -- @param response containing a single or multiple responses from - -- dns.query - -- @return array of strings containing service names - getUniqueServices = function( response ) - local services = {} + --- Creates a unique list of services + -- + -- @param response containing a single or multiple responses from + -- dns.query + -- @return array of strings containing service names + getUniqueServices = function( response ) + local services = {} - for _, r in ipairs(response) do - if ( r.output ) then - for _, svc in ipairs(r.output) do services[svc] = true end - else - services[r] = true - end - end + for _, r in ipairs(response) do + if ( r.output ) then + for _, svc in ipairs(r.output) do services[svc] = true end + else + services[r] = true + end + end - return services - end, + return services + end, - --- Returns the amount of currenlty active threads - -- - -- @param threads table containing the list of threads - -- @return count number containing the number of non-dead threads - threadCount = function( threads ) - local count = 0 + --- Returns the amount of currenlty active threads + -- + -- @param threads table containing the list of threads + -- @return count number containing the number of non-dead threads + threadCount = function( threads ) + local count = 0 - for thread in pairs(threads) do - if ( coroutine.status(thread) == "dead" ) then - threads[thread] = nil - else - count = count + 1 - end - end - return count - end + for thread in pairs(threads) do + if ( coroutine.status(thread) == "dead" ) then + threads[thread] = nil + else + count = count + 1 + end + end + return count + end } Comm = { - --- Gets a record from both the Answer and Additional section - -- - -- @param dtype DNS resource record type. - -- @param response Decoded DNS response. - -- @param retAll If true, return all entries, not just the first. - -- @return True if one or more answers of the required type were found - otherwise false. - -- @return Answer according to the answer fetcher for dtype or an Error message. - getRecordType = function( dtype, response, retAll ) + --- Gets a record from both the Answer and Additional section + -- + -- @param dtype DNS resource record type. + -- @param response Decoded DNS response. + -- @param retAll If true, return all entries, not just the first. + -- @return True if one or more answers of the required type were found - otherwise false. + -- @return Answer according to the answer fetcher for dtype or an Error message. + getRecordType = function( dtype, response, retAll ) - local result = {} - local status1, answers = dns.findNiceAnswer( dtype, response, retAll ) + local result = {} + local status1, answers = dns.findNiceAnswer( dtype, response, retAll ) - if status1 then - if retAll then - for _, v in ipairs(answers) do - table.insert(result, string.format("%s", v) ) - end - else - return true, answers - end - end + if status1 then + if retAll then + for _, v in ipairs(answers) do + table.insert(result, string.format("%s", v) ) + end + else + return true, answers + end + end - local status2, answers = dns.findNiceAdditional( dtype, response, retAll ) + local status2, answers = dns.findNiceAdditional( dtype, response, retAll ) - if status2 then - if retAll then - for _, v in ipairs(answers) do - table.insert(result, v) - end - else - return true, answers - end - end + if status2 then + if retAll then + for _, v in ipairs(answers) do + table.insert(result, v) + end + else + return true, answers + end + end - if not status1 and not status2 then - return false, answers - end + if not status1 and not status2 then + return false, answers + end - return true, result + return true, result - end, + end, - --- Send a query for a particular service and store the response in a table - -- - -- @param host string containing the ip to connect to - -- @param port number containing the port to connect to - -- @param svc the service record to retrieve - -- @param multiple true if responses from multiple hosts are expected - -- @param svcresponse table to which results are stored - queryService = function( host, port, svc, multiple, svcresponse ) - local condvar = nmap.condvar(svcresponse) - local status, response = dns.query( svc, { port = port, host = host, dtype="PTR", retPkt=true, retAll=true, multiple=multiple, sendCount=1, timeout=2000} ) - if not status then - stdnse.print_debug("Failed to query service: %s; Error: %s", svc, response) - return - end - svcresponse[svc] = svcresponse[svc] or {} - if ( multiple ) then - for _, r in ipairs(response) do - table.insert( svcresponse[svc], r ) - end - else - svcresponse[svc] = response - end - condvar("broadcast") - end, + --- Send a query for a particular service and store the response in a table + -- + -- @param host string containing the ip to connect to + -- @param port number containing the port to connect to + -- @param svc the service record to retrieve + -- @param multiple true if responses from multiple hosts are expected + -- @param svcresponse table to which results are stored + queryService = function( host, port, svc, multiple, svcresponse ) + local condvar = nmap.condvar(svcresponse) + local status, response = dns.query( svc, { port = port, host = host, dtype="PTR", retPkt=true, retAll=true, multiple=multiple, sendCount=1, timeout=2000} ) + if not status then + stdnse.print_debug("Failed to query service: %s; Error: %s", svc, response) + return + end + svcresponse[svc] = svcresponse[svc] or {} + if ( multiple ) then + for _, r in ipairs(response) do + table.insert( svcresponse[svc], r ) + end + else + svcresponse[svc] = response + end + condvar("broadcast") + end, - --- Decodes a record received from the queryService function - -- - -- @param response as returned by queryService - -- @param result table into which the decoded output should be stored - decodeRecords = function( response, result ) - local service, deviceinfo = {}, {} - local txt = {} - local ipv6, srv, address, port, proto + --- Decodes a record received from the queryService function + -- + -- @param response as returned by queryService + -- @param result table into which the decoded output should be stored + decodeRecords = function( response, result ) + local service, deviceinfo = {}, {} + local txt = {} + local ipv6, srv, address, port, proto - local record = ( #response.questions > 0 and response.questions[1].dname ) and response.questions[1].dname or "" + local record = ( #response.questions > 0 and response.questions[1].dname ) and response.questions[1].dname or "" - local status, ip = Comm.getRecordType( dns.types.A, response, false ) - if status then address = ip end + local status, ip = Comm.getRecordType( dns.types.A, response, false ) + if status then address = ip end - status, ipv6 = Comm.getRecordType( dns.types.AAAA, response, false ) - if status then - address = address or "" - address = address .. " " .. ipv6 - end + status, ipv6 = Comm.getRecordType( dns.types.AAAA, response, false ) + if status then + address = address or "" + address = address .. " " .. ipv6 + end - status, txt = Comm.getRecordType( dns.types.TXT, response, true ) - if status then - for _, v in ipairs(txt) do - if v:len() > 0 then - table.insert(service, v) - end - end - end + status, txt = Comm.getRecordType( dns.types.TXT, response, true ) + if status then + for _, v in ipairs(txt) do + if v:len() > 0 then + table.insert(service, v) + end + end + end - status, srv = Comm.getRecordType( dns.types.SRV, response, false ) - if status then - local srvparams = stdnse.strsplit( ":", srv ) + status, srv = Comm.getRecordType( dns.types.SRV, response, false ) + if status then + local srvparams = stdnse.strsplit( ":", srv ) - if #srvparams > 3 then - port = srvparams[3] - end - end + if #srvparams > 3 then + port = srvparams[3] + end + end - if address then - table.insert( service, ("Address=%s"):format( address ) ) - end + if address then + table.insert( service, ("Address=%s"):format( address ) ) + end - if record == "_device-info._tcp.local" then - service.name = "Device Information" - deviceinfo = service - table.insert(result, deviceinfo) - else - local serviceparams = stdnse.strsplit("[.]", record) + if record == "_device-info._tcp.local" then + service.name = "Device Information" + deviceinfo = service + table.insert(result, deviceinfo) + else + local serviceparams = stdnse.strsplit("[.]", record) - if #serviceparams > 2 then - local servicename = serviceparams[1]:sub(2) - local proto = serviceparams[2]:sub(2) + if #serviceparams > 2 then + local servicename = serviceparams[1]:sub(2) + local proto = serviceparams[2]:sub(2) - if port == nil or proto == nil or servicename == nil then - service.name = record - else - service.name = string.format( "%s/%s %s", port, proto, servicename) - end - end - table.insert( result, service ) - end - end, + if port == nil or proto == nil or servicename == nil then + service.name = record + else + service.name = string.format( "%s/%s %s", port, proto, servicename) + end + end + table.insert( result, service ) + end + end, - --- Query the mDNS resolvers for a list of their services - -- - -- @param host table as received by the action function - -- @param port number specifying the port to connect to - -- @param multiple receive multiple responses (multicast) - -- @return True if a dns response was received and contained an answer of - -- the requested type, or the decoded dns response was requested - -- (retPkt) and is being returned - or False otherwise. - -- @return String answer of the requested type, Table of answers or a - -- String error message of one of the following: - -- "No Such Name", "No Servers", "No Answers", - -- "Unable to handle response" - queryAllServices = function( host, port, multiple ) - local sendCount, timeout = 1, 2000 - if ( multiple ) then - sendCount, timeout = 2, 5000 - end - return dns.query( "_services._dns-sd._udp.local", { port = port, host = ( host.ip or host ), dtype="PTR", retAll=true, multiple=multiple, sendCount=sendCount, timeout=timeout } ) - end, + --- Query the mDNS resolvers for a list of their services + -- + -- @param host table as received by the action function + -- @param port number specifying the port to connect to + -- @param multiple receive multiple responses (multicast) + -- @return True if a dns response was received and contained an answer of + -- the requested type, or the decoded dns response was requested + -- (retPkt) and is being returned - or False otherwise. + -- @return String answer of the requested type, Table of answers or a + -- String error message of one of the following: + -- "No Such Name", "No Servers", "No Answers", + -- "Unable to handle response" + queryAllServices = function( host, port, multiple ) + local sendCount, timeout = 1, 2000 + if ( multiple ) then + sendCount, timeout = 2, 5000 + end + return dns.query( "_services._dns-sd._udp.local", { port = port, host = ( host.ip or host ), dtype="PTR", retAll=true, multiple=multiple, sendCount=sendCount, timeout=timeout } ) + end, } Helper = { - --- Creates a new helper instance - -- - -- @param host string containing the host name or ip - -- @param port number containing the port to connect to - -- @return o a new instance of Helper - new = function( self, host, port ) - local o = {} - setmetatable(o, self) - self.__index = self - o.host = host - o.port = port - o.mcast = false - return o - end, + --- Creates a new helper instance + -- + -- @param host string containing the host name or ip + -- @param port number containing the port to connect to + -- @return o a new instance of Helper + new = function( self, host, port ) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.mcast = false + return o + end, - --- Instructs the helper to use unconnected sockets supporting multicast - -- - -- @param mcast boolean true if multicast is to be used, false otherwise - setMulticast = function( self, mcast ) - assert( type(mcast)=="boolean", "mcast has to be either true or false") - self.mcast = mcast - end, + --- Instructs the helper to use unconnected sockets supporting multicast + -- + -- @param mcast boolean true if multicast is to be used, false otherwise + setMulticast = function( self, mcast ) + assert( type(mcast)=="boolean", "mcast has to be either true or false") + self.mcast = mcast + end, - --- Performs a DNS-SD query against a host - -- - -- @param host table as received by the action function - -- @param port number specifying the port to connect to - -- @param service string or table with the service(s) to query eg. - -- _ssh._tcp.local, _afpovertcp._tcp.local - -- if nil defaults to _services._dns-sd._udp.local (all) - -- @param mcast boolean true if a multicast query is to be done - -- @return status true on success, false on failure - -- @return response table suitable for stdnse.format_output - queryServices = function( self, service ) - local result = {} - local status, response - local mcast = self.mcast - local port = self.port or 5353 - local family = nmap.address_family() - local host = mcast and (family=="inet6" and "ff02::fb" or "224.0.0.251") or self.host - local service = service or stdnse.get_script_args('dnssd.services') + --- Performs a DNS-SD query against a host + -- + -- @param host table as received by the action function + -- @param port number specifying the port to connect to + -- @param service string or table with the service(s) to query eg. + -- _ssh._tcp.local, _afpovertcp._tcp.local + -- if nil defaults to _services._dns-sd._udp.local (all) + -- @param mcast boolean true if a multicast query is to be done + -- @return status true on success, false on failure + -- @return response table suitable for stdnse.format_output + queryServices = function( self, service ) + local result = {} + local status, response + local mcast = self.mcast + local port = self.port or 5353 + local family = nmap.address_family() + local host = mcast and (family=="inet6" and "ff02::fb" or "224.0.0.251") or self.host + local service = service or stdnse.get_script_args('dnssd.services') - if ( not(service) ) then - status, response = Comm.queryAllServices( host, port, mcast ) - if ( not(status) ) then return status, response end - else - if ( 'string' == type(service) ) then - response = { service } - elseif ( 'table' == type(service) ) then - response = service - end - end + if ( not(service) ) then + status, response = Comm.queryAllServices( host, port, mcast ) + if ( not(status) ) then return status, response end + else + if ( 'string' == type(service) ) then + response = { service } + elseif ( 'table' == type(service) ) then + response = service + end + end - response = Util.getUniqueServices(response) + response = Util.getUniqueServices(response) - local svcresponse = {} - local condvar = nmap.condvar( svcresponse ) - local threads = {} + local svcresponse = {} + local condvar = nmap.condvar( svcresponse ) + local threads = {} - for svc in pairs(response) do - local co = stdnse.new_thread( Comm.queryService, (host.ip or host), port, svc, mcast, svcresponse ) - threads[co] = true - end + for svc in pairs(response) do + local co = stdnse.new_thread( Comm.queryService, (host.ip or host), port, svc, mcast, svcresponse ) + threads[co] = true + end - -- Wait for all threads to finish running - while Util.threadCount(threads)>0 do condvar("wait") end + -- Wait for all threads to finish running + while Util.threadCount(threads)>0 do condvar("wait") end - local ipsvctbl = {} - if ( mcast ) then - -- Process all records that were returned - for svcname, response in pairs(svcresponse) do - for _, r in ipairs( response ) do - ipsvctbl[r.peer] = ipsvctbl[r.peer] or {} - Comm.decodeRecords( r.output, ipsvctbl[r.peer] ) - end - end - else - -- Process all records that were returned - for svcname, response in pairs(svcresponse) do - Comm.decodeRecords( response, result ) - end - end + local ipsvctbl = {} + if ( mcast ) then + -- Process all records that were returned + for svcname, response in pairs(svcresponse) do + for _, r in ipairs( response ) do + ipsvctbl[r.peer] = ipsvctbl[r.peer] or {} + Comm.decodeRecords( r.output, ipsvctbl[r.peer] ) + end + end + else + -- Process all records that were returned + for svcname, response in pairs(svcresponse) do + Comm.decodeRecords( response, result ) + end + end - if ( mcast ) then - -- Restructure and build our output table - for ip, svctbl in pairs( ipsvctbl ) do - table.sort(svctbl, Util.serviceCompare) - svctbl.name = ip - if target.ALLOW_NEW_TARGETS then target.add(ip) end - table.insert( result, svctbl ) - end - table.sort( result, Util.ipCompare ) - else - -- sort the tables per port - table.sort( result, Util.serviceCompare ) - end - return true, result - end, + if ( mcast ) then + -- Restructure and build our output table + for ip, svctbl in pairs( ipsvctbl ) do + table.sort(svctbl, Util.serviceCompare) + svctbl.name = ip + if target.ALLOW_NEW_TARGETS then target.add(ip) end + table.insert( result, svctbl ) + end + table.sort( result, Util.ipCompare ) + else + -- sort the tables per port + table.sort( result, Util.serviceCompare ) + end + return true, result + end, } diff --git a/nselib/eap.lua b/nselib/eap.lua index b36ad9a9b..5ae5e5cb8 100644 --- a/nselib/eap.lua +++ b/nselib/eap.lua @@ -20,7 +20,7 @@ -- local packet = eap.parse(l2_data .. l3_data3) -- if packet then -- if packet.eap.type == eap.eap_t.IDENTITY and packet.eap.code == eap.code_t.REQUEST then --- eap.send_identity_response(iface, packet.eap.id, "anonymous") +-- eap.send_identity_response(iface, packet.eap.id, "anonymous") -- end -- end -- @@ -49,242 +49,242 @@ local EAPOL_HEADER_SIZE = 4 local EAP_HEADER_SIZE = 5 eapol_t = { - PACKET = 0, - START = 1, - LOGOFF = 2, - KEY = 3, - ASF = 4, + PACKET = 0, + START = 1, + LOGOFF = 2, + KEY = 3, + ASF = 4, } eapol_str = { - [0] = "EAP Packet", - [1] = "EAPOL Start", - [2] = "EAPOL Logoff", - [3] = "EAPOL Key", - [4] = "EAPOL Encapsulated ASF Alert", + [0] = "EAP Packet", + [1] = "EAPOL Start", + [2] = "EAPOL Logoff", + [3] = "EAPOL Key", + [4] = "EAPOL Encapsulated ASF Alert", } code_t = { - REQUEST = 1, - RESPONSE = 2, - SUCCESS = 3, - FAILURE = 4, - INITIATE = 5, - FINISH = 6, + REQUEST = 1, + RESPONSE = 2, + SUCCESS = 3, + FAILURE = 4, + INITIATE = 5, + FINISH = 6, } code_str = { - [1] = "Request", - [2] = "Response", - [3] = "Success", - [4] = "Failure", - [5] = "Initiate", - [6] = "Finish", + [1] = "Request", + [2] = "Response", + [3] = "Success", + [4] = "Failure", + [5] = "Initiate", + [6] = "Finish", } eap_t = { - IDENTITY = 1, - NAK = 3, - MD5 = 4, - TLS = 13, - TTLS = 21, - PEAP = 25, - MSCHAP = 29, + IDENTITY = 1, + NAK = 3, + MD5 = 4, + TLS = 13, + TTLS = 21, + PEAP = 25, + MSCHAP = 29, } eap_str = { - [0] = "Reserved", - [1] = "Identity", - [2] = "Notification", - [3] = "Legacy Nak", - [4] = "MD5-Challenge", - [5] = "One-Time Password (OTP)", - [6] = "Generic Token Card (GTC)", - [7] = "Allocated", - [8] = "Allocated", - [9] = "RSA Public Key Authentication", - [10] = "DSS Unilateral", - [11] = "KEA", - [12] = "KEA-VALIDATE", - [13] = "EAP-TLS", - [14] = "Defender Token (AXENT)", - [15] = "RSA Security SecurID EAP", - [16] = "Arcot Systems EAP", - [17] = "EAP-Cisco Wireless", - [18] = "GSM Subscriber Identity Modules (EAP-SIM)", - [19] = "SRP-SHA1", - [20] = "Unassigned", - [21] = "EAP-TTLS", - [22] = "Remote Access Service", - [23] = "EAP-AKA Authentication", - [24] = "EAP-3Com Wireless", - [25] = "PEAP", - [26] = "MS-EAP-Authentication", - [27] = "Mutual Authentication w/Key Exchange (MAKE)", - [28] = "CRYPTOCard", - [29] = "EAP-MSCHAP-V2", - [30] = "DynamID", - [31] = "Rob EAP", - [32] = "Protected One-Time Password", - [33] = "MS-Authentication-TLV", - [34] = "SentriNET", - [35] = "EAP-Actiontec Wireless", - [36] = "Cogent Systems Biometrics Authentication EAP", - [37] = "AirFortress EAP", - [38] = "EAP-HTTP Digest", - [39] = "SecureSuite EAP", - [40] = "DeviceConnect EAP", - [41] = "EAP-SPEKE", - [42] = "EAP-MOBAC", - [43] = "EAP-FAST", - [44] = "ZoneLabs EAP (ZLXEAP)", - [45] = "EAP-Link", - [46] = "EAP-PAX", - [47] = "EAP-PSK", - [48] = "EAP-SAKE", - [49] = "EAP-IKEv2", - [50] = "EAP-AKA'", - [51] = "EAP-GPSK", - [52] = "EAP-pwd", - [53] = "EAP-EKE Version 1", - -- 54-253 Unassigned - [254] = "Reserved for the Expanded Type", - [255] = "Experimental", + [0] = "Reserved", + [1] = "Identity", + [2] = "Notification", + [3] = "Legacy Nak", + [4] = "MD5-Challenge", + [5] = "One-Time Password (OTP)", + [6] = "Generic Token Card (GTC)", + [7] = "Allocated", + [8] = "Allocated", + [9] = "RSA Public Key Authentication", + [10] = "DSS Unilateral", + [11] = "KEA", + [12] = "KEA-VALIDATE", + [13] = "EAP-TLS", + [14] = "Defender Token (AXENT)", + [15] = "RSA Security SecurID EAP", + [16] = "Arcot Systems EAP", + [17] = "EAP-Cisco Wireless", + [18] = "GSM Subscriber Identity Modules (EAP-SIM)", + [19] = "SRP-SHA1", + [20] = "Unassigned", + [21] = "EAP-TTLS", + [22] = "Remote Access Service", + [23] = "EAP-AKA Authentication", + [24] = "EAP-3Com Wireless", + [25] = "PEAP", + [26] = "MS-EAP-Authentication", + [27] = "Mutual Authentication w/Key Exchange (MAKE)", + [28] = "CRYPTOCard", + [29] = "EAP-MSCHAP-V2", + [30] = "DynamID", + [31] = "Rob EAP", + [32] = "Protected One-Time Password", + [33] = "MS-Authentication-TLV", + [34] = "SentriNET", + [35] = "EAP-Actiontec Wireless", + [36] = "Cogent Systems Biometrics Authentication EAP", + [37] = "AirFortress EAP", + [38] = "EAP-HTTP Digest", + [39] = "SecureSuite EAP", + [40] = "DeviceConnect EAP", + [41] = "EAP-SPEKE", + [42] = "EAP-MOBAC", + [43] = "EAP-FAST", + [44] = "ZoneLabs EAP (ZLXEAP)", + [45] = "EAP-Link", + [46] = "EAP-PAX", + [47] = "EAP-PSK", + [48] = "EAP-SAKE", + [49] = "EAP-IKEv2", + [50] = "EAP-AKA'", + [51] = "EAP-GPSK", + [52] = "EAP-pwd", + [53] = "EAP-EKE Version 1", + -- 54-253 Unassigned + [254] = "Reserved for the Expanded Type", + [255] = "Experimental", } local make_eapol = function (arg) - if not arg.type then arg.type = eapol_t.PACKET end - if not arg.version then arg.version = 1 end - if not arg.payload then arg.payload = "" end - if not arg.src then return nil end + if not arg.type then arg.type = eapol_t.PACKET end + if not arg.version then arg.version = 1 end + if not arg.payload then arg.payload = "" end + if not arg.src then return nil end - local p = packet.Frame:new() - p.mac_src = arg.src - p.mac_dst = packet.mactobin(ETHER_BROADCAST) - p.ether_type = ETHER_TYPE_EAPOL + local p = packet.Frame:new() + p.mac_src = arg.src + p.mac_dst = packet.mactobin(ETHER_BROADCAST) + p.ether_type = ETHER_TYPE_EAPOL - local bin_payload = bin.pack(">A",arg.payload) - p.buf = bin.pack("C",arg.version) .. bin.pack("C",arg.type) .. bin.pack(">S",bin_payload:len()).. bin_payload - p:build_ether_frame() - return p.frame_buf + local bin_payload = bin.pack(">A",arg.payload) + p.buf = bin.pack("C",arg.version) .. bin.pack("C",arg.type) .. bin.pack(">S",bin_payload:len()).. bin_payload + p:build_ether_frame() + return p.frame_buf end local make_eap = function (arg) - if not arg.code then arg.code = code_t.REQUEST end - if not arg.id then arg.id = math.random(0,255) end - if not arg.type then arg.type = eap_t.IDENTITY end - if not arg.payload then arg.payload = "" end - if not arg.header then return nil end + if not arg.code then arg.code = code_t.REQUEST end + if not arg.id then arg.id = math.random(0,255) end + if not arg.type then arg.type = eap_t.IDENTITY end + if not arg.payload then arg.payload = "" end + if not arg.header then return nil end - local bin_payload = bin.pack(">A",arg.payload) - arg.header.payload = bin.pack("C",arg.code) .. bin.pack("C",arg.id) .. bin.pack(">S",bin_payload:len() + EAP_HEADER_SIZE).. bin.pack("C",arg.type) .. bin_payload + local bin_payload = bin.pack(">A",arg.payload) + arg.header.payload = bin.pack("C",arg.code) .. bin.pack("C",arg.id) .. bin.pack(">S",bin_payload:len() + EAP_HEADER_SIZE).. bin.pack("C",arg.type) .. bin_payload - local v = make_eapol(arg.header) - stdnse.print_debug(2, "make eapol %s", arg.header.src) + local v = make_eapol(arg.header) + stdnse.print_debug(2, "make eapol %s", arg.header.src) - return v + return v end parse = function (packet) - local tb = {} - local _ + local tb = {} + local _ - stdnse.print_debug(2, "packet size: 0x%x", #packet ) + stdnse.print_debug(2, "packet size: 0x%x", #packet ) - -- parsing ethernet header - _, tb.mac_src, tb.mac_dst, tb.ether_type = bin.unpack(">A6A6S", packet) - _, tb.mac_src_str, tb.mac_dst_str = bin.unpack(">H6H6", packet) + -- parsing ethernet header + _, tb.mac_src, tb.mac_dst, tb.ether_type = bin.unpack(">A6A6S", packet) + _, tb.mac_src_str, tb.mac_dst_str = bin.unpack(">H6H6", packet) - -- parsing eapol header - _, tb.version, tb.type, tb.length = bin.unpack(">CCS", packet, ETHER_HEADER_SIZE + 1) + -- parsing eapol header + _, tb.version, tb.type, tb.length = bin.unpack(">CCS", packet, ETHER_HEADER_SIZE + 1) - stdnse.print_debug(1, "mac_src: %s, mac_dest: %s, ether_type: 0x%X", - tb.mac_src_str, tb.mac_dst_str, tb.ether_type) + stdnse.print_debug(1, "mac_src: %s, mac_dest: %s, ether_type: 0x%X", + tb.mac_src_str, tb.mac_dst_str, tb.ether_type) - if tb.ether_type ~= ETHER_TYPE_EAPOL_N then return nil, "not an eapol packet" end + if tb.ether_type ~= ETHER_TYPE_EAPOL_N then return nil, "not an eapol packet" end - stdnse.print_debug(2, "version: %X, type: %s, length: 0x%X", - tb.version, eapol_str[tb.type] or "unknown", - tb.length) + stdnse.print_debug(2, "version: %X, type: %s, length: 0x%X", + tb.version, eapol_str[tb.type] or "unknown", + tb.length) - tb.eap = {} + tb.eap = {} - if tb.length > 0 then - -- parsing body + if tb.length > 0 then + -- parsing body - _, tb.eap.code, tb.eap.id, tb.eap.length, tb.eap.type = bin.unpack(">CCSC", packet, - ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + 1) - stdnse.print_debug(2, "code: %s, id: 0x%X, length: 0x%X, type: %s", - code_str[tb.eap.code] or "unknown", - tb.eap.id, tb.eap.length, eap_str[tb.eap.type] or "unknown" ) - if tb.length ~= tb.eap.length then - stdnse.print_debug(1, "WARNING length mismatch: 0x%X and 0x%X", tb.length, tb.eap.length ) - end - end + _, tb.eap.code, tb.eap.id, tb.eap.length, tb.eap.type = bin.unpack(">CCSC", packet, + ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + 1) + stdnse.print_debug(2, "code: %s, id: 0x%X, length: 0x%X, type: %s", + code_str[tb.eap.code] or "unknown", + tb.eap.id, tb.eap.length, eap_str[tb.eap.type] or "unknown" ) + if tb.length ~= tb.eap.length then + stdnse.print_debug(1, "WARNING length mismatch: 0x%X and 0x%X", tb.length, tb.eap.length ) + end + end - tb.eap.body = {} + tb.eap.body = {} - -- parsing payload - if tb.length > 5 and tb.eap.type == eap_t.IDENTITY then - _, tb.eap.body.identity = bin.unpack("z", packet, - ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + EAP_HEADER_SIZE + 1) - stdnse.print_debug(1, "identity: %s", tb.eap.body.identity ) - end + -- parsing payload + if tb.length > 5 and tb.eap.type == eap_t.IDENTITY then + _, tb.eap.body.identity = bin.unpack("z", packet, + ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + EAP_HEADER_SIZE + 1) + stdnse.print_debug(1, "identity: %s", tb.eap.body.identity ) + end - if tb.length > 5 and tb.eap.type == eap_t.MD5 then - _, tb.eap.body.challenge = bin.unpack("p", packet, ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + EAP_HEADER_SIZE + 1) - end + if tb.length > 5 and tb.eap.type == eap_t.MD5 then + _, tb.eap.body.challenge = bin.unpack("p", packet, ETHER_HEADER_SIZE + EAPOL_HEADER_SIZE + EAP_HEADER_SIZE + 1) + end - return tb + return tb end send_identity_response = function (iface, id, identity) - if not iface then - stdnse.print_debug(1, "no interface given") - return - end + if not iface then + stdnse.print_debug(1, "no interface given") + return + end - local dnet = nmap.new_dnet() - local tb = {src = iface.mac, type = eapol_t.PACKET} - local response = make_eap{header = tb, code = code_t.RESPONSE, type = eap_t.IDENTITY, id = id, payload = identity} + local dnet = nmap.new_dnet() + local tb = {src = iface.mac, type = eapol_t.PACKET} + local response = make_eap{header = tb, code = code_t.RESPONSE, type = eap_t.IDENTITY, id = id, payload = identity} - dnet:ethernet_open(iface.device) - dnet:ethernet_send(response) - dnet:ethernet_close() + dnet:ethernet_open(iface.device) + dnet:ethernet_send(response) + dnet:ethernet_close() end send_nak_response = function (iface, id, auth) - if not iface then - stdnse.print_debug(1, "no interface given") - return - end + if not iface then + stdnse.print_debug(1, "no interface given") + return + end - local dnet = nmap.new_dnet() - local tb = {src = iface.mac, type = eapol_t.PACKET} - local response = make_eap{header = tb, code = code_t.RESPONSE, type = eap_t.NAK, id = id, payload = bin.pack("C",auth)} + local dnet = nmap.new_dnet() + local tb = {src = iface.mac, type = eapol_t.PACKET} + local response = make_eap{header = tb, code = code_t.RESPONSE, type = eap_t.NAK, id = id, payload = bin.pack("C",auth)} - dnet:ethernet_open(iface.device) - dnet:ethernet_send(response) - dnet:ethernet_close() + dnet:ethernet_open(iface.device) + dnet:ethernet_send(response) + dnet:ethernet_close() end send_start = function (iface) - if not iface then - stdnse.print_debug(1, "no interface given") - return - end + if not iface then + stdnse.print_debug(1, "no interface given") + return + end - local dnet = nmap.new_dnet() - local start = make_eapol{type = eapol_t.START, src = iface.mac} + local dnet = nmap.new_dnet() + local start = make_eapol{type = eapol_t.START, src = iface.mac} - dnet:ethernet_open(iface.device) - dnet:ethernet_send(start) - dnet:ethernet_close() + dnet:ethernet_open(iface.device) + dnet:ethernet_send(start) + dnet:ethernet_close() end diff --git a/nselib/eigrp.lua b/nselib/eigrp.lua index 1732c4005..6014933f5 100644 --- a/nselib/eigrp.lua +++ b/nselib/eigrp.lua @@ -15,375 +15,375 @@ _ENV = stdnse.module("eigrp", stdnse.seeall) -- TLV Type constants TLV = { - PARAM = 0x0001, - AUTH = 0x0002, - SEQ = 0x0003, - SWVER = 0x0004, - MSEQ = 0x0005, - STUB = 0x0006, - TERM = 0x0007, - TIDLIST = 0x0008, - REQ = 0x0101, - INT = 0x0102, - EXT = 0x0103, - COM = 0x0104, - INT6 = 0x0402, - EXT6 = 0x0403, - COM6 = 0x0404, + PARAM = 0x0001, + AUTH = 0x0002, + SEQ = 0x0003, + SWVER = 0x0004, + MSEQ = 0x0005, + STUB = 0x0006, + TERM = 0x0007, + TIDLIST = 0x0008, + REQ = 0x0101, + INT = 0x0102, + EXT = 0x0103, + COM = 0x0104, + INT6 = 0x0402, + EXT6 = 0x0403, + COM6 = 0x0404, } -- External protocols constants EXT_PROTO = { - NULL = 0x00, - IGRP = 0x01, - EIGRP = 0x02, - Static = 0x03, - RIP = 0x04, - HELLO = 0x05, - OSPF = 0x06, - ISIS = 0x07, - EGP = 0x08, - BGP = 0x09, - IDRP = 0x10, - Connected = 0x11, + NULL = 0x00, + IGRP = 0x01, + EIGRP = 0x02, + Static = 0x03, + RIP = 0x04, + HELLO = 0x05, + OSPF = 0x06, + ISIS = 0x07, + EGP = 0x08, + BGP = 0x09, + IDRP = 0x10, + Connected = 0x11, } -- Packets opcode constants OPCODE = { - UPDATE = 0x01, - RESERVED = 0x02, - QUERY = 0x03, - REPLY = 0x04, - HELLO = 0x05, + UPDATE = 0x01, + RESERVED = 0x02, + QUERY = 0x03, + REPLY = 0x04, + HELLO = 0x05, } -- The EIGRP Class EIGRP = { - --- Creates a new instance of EIGRP class. - -- @param opcode integer Opcode. Defaults to 5 (Hello) - -- @param as integer Autonomous System. Defaults to 0. - -- @param routerid integer virtual router ID. defaults to 0. - -- @param flags integer flags field value. Defaults to 0. - -- @param seq integer sequence value. Defaults to 0. - -- @param ack integer acknowledge value. Defaults to 0. - -- @param Checksum integer EIGRP packet checksum. Calculated automatically - -- if not manually set. - -- @param Table TLVs table. - -- @return o Instance of EIGRP - new = function(self, opcode, as, routerid, flags, seq, ack, checksum, tlvs) - local o = { - ver = 2, - opcode = opcode or TLV.HELLO, - as = as or 0, - routerid = routerid or 0, - flags = flags or 0, - seq = seq or 0x00, - ack = ack or 0x00, - checksum = checksum, - tlvs = tlvs or {}, - } - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new instance of EIGRP class. + -- @param opcode integer Opcode. Defaults to 5 (Hello) + -- @param as integer Autonomous System. Defaults to 0. + -- @param routerid integer virtual router ID. defaults to 0. + -- @param flags integer flags field value. Defaults to 0. + -- @param seq integer sequence value. Defaults to 0. + -- @param ack integer acknowledge value. Defaults to 0. + -- @param Checksum integer EIGRP packet checksum. Calculated automatically + -- if not manually set. + -- @param Table TLVs table. + -- @return o Instance of EIGRP + new = function(self, opcode, as, routerid, flags, seq, ack, checksum, tlvs) + local o = { + ver = 2, + opcode = opcode or TLV.HELLO, + as = as or 0, + routerid = routerid or 0, + flags = flags or 0, + seq = seq or 0x00, + ack = ack or 0x00, + checksum = checksum, + tlvs = tlvs or {}, + } + setmetatable(o, self) + self.__index = self + return o + end, - --- Parses a raw eigrp packet and returns a structred response. - -- @param eigrp_raw string EIGRP Raw packet. - -- @return response table Structured eigrp packet. - parse = function(eigrp_raw) - if type(eigrp_raw) ~= 'string' then - stdnse.print_debug("eigrp.lua: parse input should be string.") - return - end - if #eigrp_raw < 20 then - stdnse.print_debug("eigrp.lua: raw packet size lower then 20.") - return - end - local tlv - local eigrp_packet = {} - local index = 1 - index, eigrp_packet.ver = bin.unpack(">C", eigrp_raw, index) - index, eigrp_packet.opcode = bin.unpack(">C", eigrp_raw, index) - index, eigrp_packet.checksum = bin.unpack(">S", eigrp_raw, index) - index, eigrp_packet.flags = bin.unpack(">I", eigrp_raw, index) - index, eigrp_packet.seq = bin.unpack(">I", eigrp_raw, index) - index, eigrp_packet.ack = bin.unpack(">I", eigrp_raw, index) - index, eigrp_packet.routerid = bin.unpack(">S", eigrp_raw, index) - index, eigrp_packet.as = bin.unpack(">S", eigrp_raw, index) - eigrp_packet.tlvs = {} - while index < #eigrp_raw do - tlv = {} - index, tlv.type = bin.unpack(">S", eigrp_raw, index) - index, tlv.length = bin.unpack(">S", eigrp_raw, index) - if tlv.length == 0x00 then - -- In case someone wants to DoS us :) - stdnse.print_debug("eigrp.lua: stopped parsing due to null TLV length.") - break - end - if tlv.type == TLV.PARAM then - -- Parameters - local k = {} - index, k[1], k[2], k[3], k[4], k[5], k[6] = bin.unpack(">CCCCCC", eigrp_raw, index) - index, tlv.htime = bin.unpack(">S", eigrp_raw, index) - index = index + tlv.length - 12 - elseif tlv.type == TLV.AUTH then - index, tlv.authtype = bin.unpack(">S", eigrp_raw, index) - index, tlv.authlen = bin.unpack(">S", eigrp_raw, index) - index, tlv.keyid = bin.unpack(">I", eigrp_raw, index) - index, tlv.keyseq = bin.unpack(">I", eigrp_raw, index) - -- Null pad == tlv.length - What was already parsed - authlen - index, tlv.digest = bin.unpack(">S", eigrp_raw, index + (tlv.length - tlv.authlen - index + 1)) - elseif tlv.type == TLV.SEQ then - -- Sequence - index, tlv.addlen = bin.unpack(">S", eigrp_raw, index) - index, tlv.address = bin.unpack("C", eigrp_raw, index) - index, tlv.minv = bin.unpack(">C", eigrp_raw, index) - index, tlv.majtlv = bin.unpack(">C", eigrp_raw, index) - index, tlv.mintlv = bin.unpack(">C", eigrp_raw, index) - index = index + tlv.length - 8 - elseif tlv.type == TLV.MSEQ then - -- Next Multicast Sequence - index, tlv.mseq = bin.unpack(">I", eigrp_raw, index) - index = index + tlv.length - 8 - elseif tlv.type == TLV.STUB then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.TERM then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.TIDLIST then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.REQ then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.INT then - -- Internal Route - index, tlv.nexth = bin.unpack("S", eigrp_raw, index + 15) - -- Destination varies in length - -- e.g trailing 0's are omitted - -- if length = 29 => destination is 4 bytes - -- if length = 28 => destination is 3 bytes - -- if length = 27 => destination is 2 bytes - -- if length = 26 => destination is 1 byte - local dst = {} - index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 29, eigrp_raw, index) - for i=2,4 do - if not dst[i] then - dst[i] = '0' - end - end - tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] - elseif tlv.type == TLV.EXT then - -- External Route - index, tlv.nexth = bin.unpack("I", eigrp_raw, index) - index, tlv.tag = bin.unpack(">I", eigrp_raw, index) - index, tlv.emetric = bin.unpack(">I", eigrp_raw, index) - -- Skip 2 reserved bytes - index, tlv.eproto = bin.unpack(">C", eigrp_raw, index + 2) - index, tlv.eflags = bin.unpack(">C", eigrp_raw, index) - index, tlv.lmetrics = bin.unpack(">L"..2, eigrp_raw, index) - index, tlv.mask = bin.unpack(">C", eigrp_raw, index) - -- Destination varies in length - -- if length = 49 => destination is 4 bytes - -- if length = 48 => destination is 3 bytes - -- if length = 47 => destination is 2 bytes - -- if length = 46 => destination is 1 byte - local dst = {} - index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 49, eigrp_raw, index) - for i=2,4 do - if not dst[i] then - dst[i] = '0' - end - end - tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] - elseif tlv.type == TLV.COM then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.INT6 then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.EXT6 then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - elseif tlv.type == TLV.COM6 then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - index = index + tlv.length - 4 - else - stdnse.print_debug("eigrp.lua: eigrp.lua: TLV type %d unknown.", tlv.type) - index = index + tlv.length - 4 - end - table.insert(eigrp_packet.tlvs, tlv) - end - return eigrp_packet - end, + --- Parses a raw eigrp packet and returns a structred response. + -- @param eigrp_raw string EIGRP Raw packet. + -- @return response table Structured eigrp packet. + parse = function(eigrp_raw) + if type(eigrp_raw) ~= 'string' then + stdnse.print_debug("eigrp.lua: parse input should be string.") + return + end + if #eigrp_raw < 20 then + stdnse.print_debug("eigrp.lua: raw packet size lower then 20.") + return + end + local tlv + local eigrp_packet = {} + local index = 1 + index, eigrp_packet.ver = bin.unpack(">C", eigrp_raw, index) + index, eigrp_packet.opcode = bin.unpack(">C", eigrp_raw, index) + index, eigrp_packet.checksum = bin.unpack(">S", eigrp_raw, index) + index, eigrp_packet.flags = bin.unpack(">I", eigrp_raw, index) + index, eigrp_packet.seq = bin.unpack(">I", eigrp_raw, index) + index, eigrp_packet.ack = bin.unpack(">I", eigrp_raw, index) + index, eigrp_packet.routerid = bin.unpack(">S", eigrp_raw, index) + index, eigrp_packet.as = bin.unpack(">S", eigrp_raw, index) + eigrp_packet.tlvs = {} + while index < #eigrp_raw do + tlv = {} + index, tlv.type = bin.unpack(">S", eigrp_raw, index) + index, tlv.length = bin.unpack(">S", eigrp_raw, index) + if tlv.length == 0x00 then + -- In case someone wants to DoS us :) + stdnse.print_debug("eigrp.lua: stopped parsing due to null TLV length.") + break + end + if tlv.type == TLV.PARAM then + -- Parameters + local k = {} + index, k[1], k[2], k[3], k[4], k[5], k[6] = bin.unpack(">CCCCCC", eigrp_raw, index) + index, tlv.htime = bin.unpack(">S", eigrp_raw, index) + index = index + tlv.length - 12 + elseif tlv.type == TLV.AUTH then + index, tlv.authtype = bin.unpack(">S", eigrp_raw, index) + index, tlv.authlen = bin.unpack(">S", eigrp_raw, index) + index, tlv.keyid = bin.unpack(">I", eigrp_raw, index) + index, tlv.keyseq = bin.unpack(">I", eigrp_raw, index) + -- Null pad == tlv.length - What was already parsed - authlen + index, tlv.digest = bin.unpack(">S", eigrp_raw, index + (tlv.length - tlv.authlen - index + 1)) + elseif tlv.type == TLV.SEQ then + -- Sequence + index, tlv.addlen = bin.unpack(">S", eigrp_raw, index) + index, tlv.address = bin.unpack("C", eigrp_raw, index) + index, tlv.minv = bin.unpack(">C", eigrp_raw, index) + index, tlv.majtlv = bin.unpack(">C", eigrp_raw, index) + index, tlv.mintlv = bin.unpack(">C", eigrp_raw, index) + index = index + tlv.length - 8 + elseif tlv.type == TLV.MSEQ then + -- Next Multicast Sequence + index, tlv.mseq = bin.unpack(">I", eigrp_raw, index) + index = index + tlv.length - 8 + elseif tlv.type == TLV.STUB then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.TERM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.TIDLIST then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.REQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.INT then + -- Internal Route + index, tlv.nexth = bin.unpack("S", eigrp_raw, index + 15) + -- Destination varies in length + -- e.g trailing 0's are omitted + -- if length = 29 => destination is 4 bytes + -- if length = 28 => destination is 3 bytes + -- if length = 27 => destination is 2 bytes + -- if length = 26 => destination is 1 byte + local dst = {} + index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 29, eigrp_raw, index) + for i=2,4 do + if not dst[i] then + dst[i] = '0' + end + end + tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] + elseif tlv.type == TLV.EXT then + -- External Route + index, tlv.nexth = bin.unpack("I", eigrp_raw, index) + index, tlv.tag = bin.unpack(">I", eigrp_raw, index) + index, tlv.emetric = bin.unpack(">I", eigrp_raw, index) + -- Skip 2 reserved bytes + index, tlv.eproto = bin.unpack(">C", eigrp_raw, index + 2) + index, tlv.eflags = bin.unpack(">C", eigrp_raw, index) + index, tlv.lmetrics = bin.unpack(">L"..2, eigrp_raw, index) + index, tlv.mask = bin.unpack(">C", eigrp_raw, index) + -- Destination varies in length + -- if length = 49 => destination is 4 bytes + -- if length = 48 => destination is 3 bytes + -- if length = 47 => destination is 2 bytes + -- if length = 46 => destination is 1 byte + local dst = {} + index, dst[1], dst[2], dst[3], dst[4] = bin.unpack(">C" .. 4 + tlv.length - 49, eigrp_raw, index) + for i=2,4 do + if not dst[i] then + dst[i] = '0' + end + end + tlv.dst = dst[1] .. '.' .. dst[2] .. '.' .. dst[3] .. '.' .. dst[4] + elseif tlv.type == TLV.COM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.INT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.EXT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + elseif tlv.type == TLV.COM6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + index = index + tlv.length - 4 + else + stdnse.print_debug("eigrp.lua: eigrp.lua: TLV type %d unknown.", tlv.type) + index = index + tlv.length - 4 + end + table.insert(eigrp_packet.tlvs, tlv) + end + return eigrp_packet + end, - --- Adds a TLV table to the table of TLVs. - -- @param tlv TLV table. - addTLV = function(self, tlv) - if type(tlv) == 'table' then - table.insert(self.tlvs, tlv) - else - stdnse.print_debug("eigrp.lua: TLV should be a table, not %s", type(tlv)) - end - end, + --- Adds a TLV table to the table of TLVs. + -- @param tlv TLV table. + addTLV = function(self, tlv) + if type(tlv) == 'table' then + table.insert(self.tlvs, tlv) + else + stdnse.print_debug("eigrp.lua: TLV should be a table, not %s", type(tlv)) + end + end, - --- Checks if TLV type is one that should contain routing information. - -- @param tlvtype integer TLV type integer to check. - -- @return status true if tlvtype is a routing information tlv. - isRoutingTLV = function(tlvtype) - if tlvtype == 0x101 or tlvtype == 0x102 - or tlvtype == 0x103 or tlvtype == 0x104 - or tlvtype == 0x402 or tlvtype == 0x403 - or tlvtype == 0x404 then - return true - end - end, + --- Checks if TLV type is one that should contain routing information. + -- @param tlvtype integer TLV type integer to check. + -- @return status true if tlvtype is a routing information tlv. + isRoutingTLV = function(tlvtype) + if tlvtype == 0x101 or tlvtype == 0x102 + or tlvtype == 0x103 or tlvtype == 0x104 + or tlvtype == 0x402 or tlvtype == 0x403 + or tlvtype == 0x404 then + return true + end + end, - --- Sets the EIGRP version. - -- @param ver integer version to set. - setVersion = function(self, ver) - self.ver = ver - end, - --- Sets the EIGRP Packet opcode - -- @param opcode integer EIGRP opcode. - setOpcode = function(self, opcode) - self.opcode = opcode - end, - --- Sets the EIGRP packet checksum - -- @param integer checksum Checksum to set. - setChecksum = function(self, checksum) - self.checksum = checksum - end, - --- Sets the EIGRP packet flags field. - -- @param flags Flags integer value. - setFlags = function(self, flags) - self.flags = flags + --- Sets the EIGRP version. + -- @param ver integer version to set. + setVersion = function(self, ver) + self.ver = ver + end, + --- Sets the EIGRP Packet opcode + -- @param opcode integer EIGRP opcode. + setOpcode = function(self, opcode) + self.opcode = opcode + end, + --- Sets the EIGRP packet checksum + -- @param integer checksum Checksum to set. + setChecksum = function(self, checksum) + self.checksum = checksum + end, + --- Sets the EIGRP packet flags field. + -- @param flags Flags integer value. + setFlags = function(self, flags) + self.flags = flags end, --- Sets the EIGRP packet sequence field. -- @param seq EIGRP sequence. setSequence = function(self, seq) - self.seq = seq + self.seq = seq end, --- Sets the EIGRP Packet acknowledge field. -- @param ack EIGRP acknowledge. setAcknowledge = function(self, ack) - self.ack = ack + self.ack = ack end, --- Sets the EIGRP Packet Virtual Router ID. -- @param routerid EIGRP Virtual Router ID. setRouterID = function(self, routerid) - self.routerid = routerid + self.routerid = routerid end, --- Sets the EIGRP Packet Autonomous System. -- @param as EIGRP A.S. setAS = function(self, as) - self.as = as + self.as = as end, --- Sets the EIGRP Packet tlvs -- @param tlvs table of EIGRP tlvs. setTlvs = function(self, tlvs) - self.tlvs = tlvs + self.tlvs = tlvs end, --- Converts the request to a string suitable to be sent over a socket. -- @return data string containing the complete request to send over the socket __tostring = function(self) - local data = bin.pack(">C", self.ver) -- Version 2 - data = data .. bin.pack(">C", self.opcode) -- Opcode: Hello + local data = bin.pack(">C", self.ver) -- Version 2 + data = data .. bin.pack(">C", self.opcode) -- Opcode: Hello - -- If checksum not manually. - -- set to 0, then calculate it later - if self.checksum then - data = data .. bin.pack(">S", self.checksum) - else - data = data .. bin.pack(">S", 0x0000) -- Calculated later. - end - data = data .. bin.pack(">I", self.flags) -- Flags - data = data .. bin.pack(">I", self.seq) -- Sequence 0 - data = data .. bin.pack(">I", self.ack) -- Acknowledge 0 - data = data .. bin.pack(">S", self.routerid) -- Virtual Router ID 0 - data = data .. bin.pack(">S", self.as) -- Autonomous system - for _, tlv in pairs(self.tlvs) do - if tlv.type == TLV.PARAM then - data = data .. bin.pack(">S", TLV.PARAM) - data = data .. bin.pack(">S", 0x000c) -- Length: 12 - data = data .. bin.pack(">CCCCCC", tlv.k[1],tlv.k[2],tlv.k[3], - tlv.k[4],tlv.k[5],tlv.k[6]) - data = data .. bin.pack(">S", tlv.htime) - elseif tlv.type == TLV.AUTH then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.SEQ then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.SWVER then - data = data .. bin.pack(">S", TLV.SWVER) - data = data .. bin.pack(">S", 0x0008) - data = data .. bin.pack(">CC", tonumber(tlv.majv), tonumber(tlv.minv)) - data = data .. bin.pack(">CC", tonumber(tlv.majtlv), tonumber(tlv.mintlv)) - elseif tlv.type == TLV.MSEQ then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.STUB then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.TERM then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.TIDLIST then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.REQ then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.INT then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.EXT then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.COM then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.INT6 then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.EXT6 then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - elseif tlv.type == TLV.COM6 then - -- TODO - stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) - else - stdnse.print_debug("eigrp.lua: TLV type %d unknown.", tlv.type) - end - end - -- In the end, correct the checksum if not manually set - if not self.checksum then - data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) - end - return data + -- If checksum not manually. + -- set to 0, then calculate it later + if self.checksum then + data = data .. bin.pack(">S", self.checksum) + else + data = data .. bin.pack(">S", 0x0000) -- Calculated later. + end + data = data .. bin.pack(">I", self.flags) -- Flags + data = data .. bin.pack(">I", self.seq) -- Sequence 0 + data = data .. bin.pack(">I", self.ack) -- Acknowledge 0 + data = data .. bin.pack(">S", self.routerid) -- Virtual Router ID 0 + data = data .. bin.pack(">S", self.as) -- Autonomous system + for _, tlv in pairs(self.tlvs) do + if tlv.type == TLV.PARAM then + data = data .. bin.pack(">S", TLV.PARAM) + data = data .. bin.pack(">S", 0x000c) -- Length: 12 + data = data .. bin.pack(">CCCCCC", tlv.k[1],tlv.k[2],tlv.k[3], + tlv.k[4],tlv.k[5],tlv.k[6]) + data = data .. bin.pack(">S", tlv.htime) + elseif tlv.type == TLV.AUTH then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.SEQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.SWVER then + data = data .. bin.pack(">S", TLV.SWVER) + data = data .. bin.pack(">S", 0x0008) + data = data .. bin.pack(">CC", tonumber(tlv.majv), tonumber(tlv.minv)) + data = data .. bin.pack(">CC", tonumber(tlv.majtlv), tonumber(tlv.mintlv)) + elseif tlv.type == TLV.MSEQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.STUB then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.TERM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.TIDLIST then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.REQ then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.INT then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.EXT then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.COM then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.INT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.EXT6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + elseif tlv.type == TLV.COM6 then + -- TODO + stdnse.print_debug("eigrp.lua: TLV type %d skipped due to no parser.", tlv.type) + else + stdnse.print_debug("eigrp.lua: TLV type %d unknown.", tlv.type) + end + end + -- In the end, correct the checksum if not manually set + if not self.checksum then + data = data:sub(1,2) .. bin.pack(">S", packet.in_cksum(data)) .. data:sub(5) + end + return data end, -} + } -return _ENV; + return _ENV; diff --git a/nselib/ftp.lua b/nselib/ftp.lua index f962af06c..afb715663 100644 --- a/nselib/ftp.lua +++ b/nselib/ftp.lua @@ -40,42 +40,42 @@ end -- @return numeric code or nil. -- @return text reply or error message. function read_reply(buffer) - local readline - local line, err - local code, message - local _, p, tmp + local readline + local line, err + local code, message + local _, p, tmp - line, err = buffer() - if not line then - return line, err - end + line, err = buffer() + if not line then + return line, err + end - -- Single-line response? - code, message = string.match(line, "^(%d%d%d) (.*)$") - if code then - return tonumber(code), message - end + -- Single-line response? + code, message = string.match(line, "^(%d%d%d) (.*)$") + if code then + return tonumber(code), message + end - -- Multi-line response? - _, p, code, message = string.find(line, "^(%d%d%d)%-(.*)$") - if p then - while true do - line, err = buffer() - if not line then - return line, err - end - tmp = string.match(line, "^%d%d%d (.*)$") - if tmp then - message = message .. "\n" .. tmp - break - end - message = message .. "\n" .. line - end + -- Multi-line response? + _, p, code, message = string.find(line, "^(%d%d%d)%-(.*)$") + if p then + while true do + line, err = buffer() + if not line then + return line, err + end + tmp = string.match(line, "^%d%d%d (.*)$") + if tmp then + message = message .. "\n" .. tmp + break + end + message = message .. "\n" .. line + end - return tonumber(code), message - end + return tonumber(code), message + end - return nil, string.format("Unparseable response: %q", line) + return nil, string.format("Unparseable response: %q", line) end return _ENV; diff --git a/nselib/gps.lua b/nselib/gps.lua index 85870d278..b47005178 100644 --- a/nselib/gps.lua +++ b/nselib/gps.lua @@ -15,106 +15,106 @@ _ENV = stdnse.module("gps", stdnse.seeall) NMEA = { - -- Parser for the RMC sentence - RMC = { + -- Parser for the RMC sentence + RMC = { - parse = function(str) + parse = function(str) - local time, status, latitude, ns_indicator, longitude, - ew_indicator, speed, course, date, variation, - ew_variation, checksum = str:match("^%$GPRMC,([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^%*]*)(.*)$") + local time, status, latitude, ns_indicator, longitude, + ew_indicator, speed, course, date, variation, + ew_variation, checksum = str:match("^%$GPRMC,([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^,]*),([^%*]*)(.*)$") - if ( not(latitude) or not(longitude) ) then - return - end + if ( not(latitude) or not(longitude) ) then + return + end - local deg, min = latitude:match("^(..)(.*)$") - if ( not(deg) or not(min) ) then - return - end - latitude = tonumber(deg) + (tonumber(min)/60) + local deg, min = latitude:match("^(..)(.*)$") + if ( not(deg) or not(min) ) then + return + end + latitude = tonumber(deg) + (tonumber(min)/60) - deg, min = longitude:match("^(..)(.*)$") - if ( not(deg) or not(min) ) then - return - end - longitude = tonumber(deg) + (tonumber(min)/60) - if ( ew_indicator == 'W' ) then - longitude = -longitude - end + deg, min = longitude:match("^(..)(.*)$") + if ( not(deg) or not(min) ) then + return + end + longitude = tonumber(deg) + (tonumber(min)/60) + if ( ew_indicator == 'W' ) then + longitude = -longitude + end - if ( ns_indicator == 'S' ) then - latitude = -latitude - end + if ( ns_indicator == 'S' ) then + latitude = -latitude + end - return { time = time, status = status, latitude = latitude, - longitude = longitude, speed = speed, course = course, - date = date, variation = variation, - ew_variation = ew_variation } - end, + return { time = time, status = status, latitude = latitude, + longitude = longitude, speed = speed, course = course, + date = date, variation = variation, + ew_variation = ew_variation } + end, - }, + }, - -- Calculates an verifies the message checksum - -- - -- @param str containing the GPS sentence - -- @return status true on success, false if the checksum does not match - -- @return err string if status is false - checksum = function(str) - local val = 0 - for c in str:sub(2,-4):gmatch(".") do - val = bit.bxor(val, string.byte(c)) - end + -- Calculates an verifies the message checksum + -- + -- @param str containing the GPS sentence + -- @return status true on success, false if the checksum does not match + -- @return err string if status is false + checksum = function(str) + local val = 0 + for c in str:sub(2,-4):gmatch(".") do + val = bit.bxor(val, string.byte(c)) + end - if ( str:sub(-2):upper() ~= stdnse.tohex(string.char(val)):upper() ) then - return false, ("Failed to verify checksum (got: %s; expected: %s)"):format(stdnse.tohex(string.char(val)), str:sub(-2)) - end - return true - end, + if ( str:sub(-2):upper() ~= stdnse.tohex(string.char(val)):upper() ) then + return false, ("Failed to verify checksum (got: %s; expected: %s)"):format(stdnse.tohex(string.char(val)), str:sub(-2)) + end + return true + end, - -- Parses a GPS sentence using the apropriate parser - -- - -- @param str containing the GPS sentence - -- @return entry table containing the parsed response or - -- err string if status is false - -- @return status true on success, false on failure - parse = function(str) + -- Parses a GPS sentence using the apropriate parser + -- + -- @param str containing the GPS sentence + -- @return entry table containing the parsed response or + -- err string if status is false + -- @return status true on success, false on failure + parse = function(str) - local status, err = NMEA.checksum(str) - if ( not(status) ) then - return false, err - end + local status, err = NMEA.checksum(str) + if ( not(status) ) then + return false, err + end - local prefix = str:match("^%$GP([^,]*)") - if ( not(prefix) ) then - return false, "Not a NMEA sentence" - end + local prefix = str:match("^%$GP([^,]*)") + if ( not(prefix) ) then + return false, "Not a NMEA sentence" + end - if ( NMEA[prefix] and NMEA[prefix].parse ) then - local e = NMEA[prefix].parse(str) - if (not(e)) then - return false, ("Failed to parse entry: %s"):format(str) - end - return true, e - else - local err = ("No parser for prefix: %s"):format(prefix) - stdnse.print_debug(2, err) - return false, err - end + if ( NMEA[prefix] and NMEA[prefix].parse ) then + local e = NMEA[prefix].parse(str) + if (not(e)) then + return false, ("Failed to parse entry: %s"):format(str) + end + return true, e + else + local err = ("No parser for prefix: %s"):format(prefix) + stdnse.print_debug(2, err) + return false, err + end - end + end } Util = { - convertTime = function(date, time) - local d = {} - d.hour, d.min, d.sec = time:match("(..)(..)(..)") - d.day, d.month, d.year = date:match("(..)(..)(..)") - d.year = d.year + 2000 - return os.time(d) - end + convertTime = function(date, time) + local d = {} + d.hour, d.min, d.sec = time:match("(..)(..)(..)") + d.day, d.month, d.year = date:match("(..)(..)(..)") + d.year = d.year + 2000 + return os.time(d) + end } return _ENV; diff --git a/nselib/iax2.lua b/nselib/iax2.lua index 34f8474af..4f8f2f273 100644 --- a/nselib/iax2.lua +++ b/nselib/iax2.lua @@ -18,324 +18,324 @@ _ENV = stdnse.module("iax2", stdnse.seeall) IAX2 = { - FrameType = { - IAX = 6, - }, + FrameType = { + IAX = 6, + }, - SubClass = { - ACK = 0x04, - REGACK = 0x0f, - REGREJ = 0x10, - REGREL = 0x11, - CALLTOKEN = 0x28, - }, + SubClass = { + ACK = 0x04, + REGACK = 0x0f, + REGREJ = 0x10, + REGREL = 0x11, + CALLTOKEN = 0x28, + }, - InfoElement = { - USERNAME = 0x06, - CHALLENGE = 0x0f, - MD5_RESULT = 0x10, - CALLTOKEN = 0x36, - }, + InfoElement = { + USERNAME = 0x06, + CHALLENGE = 0x0f, + MD5_RESULT = 0x10, + CALLTOKEN = 0x36, + }, - PacketType = { - FULL = 1, - }, + PacketType = { + FULL = 1, + }, - -- The IAX2 Header - Header = { + -- The IAX2 Header + Header = { - -- Creates a new Header instance - -- @param src_call number containing the source call - -- @param dst_call number containing the dest call - -- @param timestamp number containing a timestamp - -- @param oseqno number containing the seqno of outgoing packets - -- @param iseqno number containing the seqno of incoming packets - -- @param frametype number containing the frame type - -- @param subclass number containing the subclass type - new = function(self, src_call, dst_call, timestamp, oseqno, iseqno, frametype, subclass) - local o = { - type = IAX2.PacketType.FULL, - retrans = false, - src_call = src_call, - dst_call = dst_call, - timestamp = timestamp, - oseqno = oseqno, - iseqno = iseqno, - frametype = frametype, - subclass = subclass, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Header instance + -- @param src_call number containing the source call + -- @param dst_call number containing the dest call + -- @param timestamp number containing a timestamp + -- @param oseqno number containing the seqno of outgoing packets + -- @param iseqno number containing the seqno of incoming packets + -- @param frametype number containing the frame type + -- @param subclass number containing the subclass type + new = function(self, src_call, dst_call, timestamp, oseqno, iseqno, frametype, subclass) + local o = { + type = IAX2.PacketType.FULL, + retrans = false, + src_call = src_call, + dst_call = dst_call, + timestamp = timestamp, + oseqno = oseqno, + iseqno = iseqno, + frametype = frametype, + subclass = subclass, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Parses data, a byte string, and creates a new Header instance - -- @return header instance of Header - parse = function(data) - local header = IAX2.Header:new() - local pos, frame_type = bin.unpack("C", data) - if ( bit.band(frame_type, 0x80) == 0 ) then - print("frame_type", stdnse.tohex(frame_type)) - stdnse.print_debug(2, "Frametype not supported") - return - end - header.type = IAX2.PacketType.FULL - pos, header.src_call = bin.unpack(">S", data) - header.src_call = bit.band(header.src_call, 0x7FFF) + -- Parses data, a byte string, and creates a new Header instance + -- @return header instance of Header + parse = function(data) + local header = IAX2.Header:new() + local pos, frame_type = bin.unpack("C", data) + if ( bit.band(frame_type, 0x80) == 0 ) then + print("frame_type", stdnse.tohex(frame_type)) + stdnse.print_debug(2, "Frametype not supported") + return + end + header.type = IAX2.PacketType.FULL + pos, header.src_call = bin.unpack(">S", data) + header.src_call = bit.band(header.src_call, 0x7FFF) - local retrans - pos, retrans = bin.unpack("C", data, pos) - if ( bit.band(retrans, 0x80) == 8 ) then - header.retrans = true - end - pos, header.dst_call = bin.unpack(">S", data, pos - 1) - header.dst_call = bit.band(header.dst_call, 0x7FFF) + local retrans + pos, retrans = bin.unpack("C", data, pos) + if ( bit.band(retrans, 0x80) == 8 ) then + header.retrans = true + end + pos, header.dst_call = bin.unpack(">S", data, pos - 1) + header.dst_call = bit.band(header.dst_call, 0x7FFF) - pos, header.timestamp, header.oseqno, - header.iseqno, header.frametype, header.subclass = bin.unpack(">ICCCC", data, pos) + pos, header.timestamp, header.oseqno, + header.iseqno, header.frametype, header.subclass = bin.unpack(">ICCCC", data, pos) - return header - end, + return header + end, - -- Converts the instance to a string - -- @return str containing the instance - __tostring = function(self) - assert(self.src_call < 32767, "Source call exceeds 32767") - assert(self.dst_call < 32767, "Dest call exceeds 32767") - local src_call = self.src_call - local dst_call = self.dst_call - if ( self.type == IAX2.PacketType.FULL ) then - src_call = src_call + 32768 - end - if ( self.retrans ) then - dst_call = dst_call + 32768 - end - return bin.pack(">SSICCCC", src_call, dst_call, self.timestamp, - self.oseqno, self.iseqno, self.frametype, self.subclass) - end, - }, + -- Converts the instance to a string + -- @return str containing the instance + __tostring = function(self) + assert(self.src_call < 32767, "Source call exceeds 32767") + assert(self.dst_call < 32767, "Dest call exceeds 32767") + local src_call = self.src_call + local dst_call = self.dst_call + if ( self.type == IAX2.PacketType.FULL ) then + src_call = src_call + 32768 + end + if ( self.retrans ) then + dst_call = dst_call + 32768 + end + return bin.pack(">SSICCCC", src_call, dst_call, self.timestamp, + self.oseqno, self.iseqno, self.frametype, self.subclass) + end, + }, - -- The IAX2 Request class - Request = { + -- The IAX2 Request class + Request = { - -- Creates a new instance - -- @param header instance of Header - new = function(self, header) - local o = { - header = header, - ies = {} - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance + -- @param header instance of Header + new = function(self, header) + local o = { + header = header, + ies = {} + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Sets an Info Element or adds one, in case it's missing - -- @param key the key value of the IE to add - -- @param value string containing the value to set or add - setIE = function(self, key, value) - for _, ie in ipairs(self.ies or {}) do - if ( key == ie.type ) then - ie.value = value - end - end - table.insert(self.ies, { type = key, value = value } ) - end, + -- Sets an Info Element or adds one, in case it's missing + -- @param key the key value of the IE to add + -- @param value string containing the value to set or add + setIE = function(self, key, value) + for _, ie in ipairs(self.ies or {}) do + if ( key == ie.type ) then + ie.value = value + end + end + table.insert(self.ies, { type = key, value = value } ) + end, - -- Gets an information element - -- @param key number containing the element number to retrieve - -- @return ie table containing the info element if it exists - getIE = function(self, key) - for _, ie in ipairs(self.ies or {}) do - if ( key == ie.type ) then - return ie - end - end - end, + -- Gets an information element + -- @param key number containing the element number to retrieve + -- @return ie table containing the info element if it exists + getIE = function(self, key) + for _, ie in ipairs(self.ies or {}) do + if ( key == ie.type ) then + return ie + end + end + end, - -- Converts the instance to a string - -- @return str containing the instance - __tostring = function(self) - local data = "" - for _, ie in ipairs(self.ies) do - data = data .. bin.pack("Cp", ie.type, ie.value ) - end + -- Converts the instance to a string + -- @return str containing the instance + __tostring = function(self) + local data = "" + for _, ie in ipairs(self.ies) do + data = data .. bin.pack("Cp", ie.type, ie.value ) + end - return tostring(self.header) .. data - end, + return tostring(self.header) .. data + end, - }, + }, - -- The IAX2 Response - Response = { + -- The IAX2 Response + Response = { - -- Creates a new instance - new = function(self) - local o = { ies = {} } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance + new = function(self) + local o = { ies = {} } + setmetatable(o, self) + self.__index = self + return o + end, - -- Sets an Info Element or adds one, in case it's missing - -- @param key the key value of the IE to add - -- @param value string containing the value to set or add - setIE = function(self, key, value) - for _, ie in ipairs(self.ies or {}) do - if ( key == ie.type ) then - ie.value = value - end - end - table.insert(self.ies, { type = key, value = value } ) - end, + -- Sets an Info Element or adds one, in case it's missing + -- @param key the key value of the IE to add + -- @param value string containing the value to set or add + setIE = function(self, key, value) + for _, ie in ipairs(self.ies or {}) do + if ( key == ie.type ) then + ie.value = value + end + end + table.insert(self.ies, { type = key, value = value } ) + end, - -- Gets an information element - -- @param key number containing the element number to retrieve - -- @return ie table containing the info element if it exists - getIE = function(self, key) - for _, ie in ipairs(self.ies or {}) do - if ( key == ie.type ) then - return ie - end - end - end, + -- Gets an information element + -- @param key number containing the element number to retrieve + -- @return ie table containing the info element if it exists + getIE = function(self, key) + for _, ie in ipairs(self.ies or {}) do + if ( key == ie.type ) then + return ie + end + end + end, - -- Parses data, a byte string, and creates a response - -- @return resp instance of response - parse = function(data) - local resp = IAX2.Response:new() - if ( not(resp) ) then return end + -- Parses data, a byte string, and creates a response + -- @return resp instance of response + parse = function(data) + local resp = IAX2.Response:new() + if ( not(resp) ) then return end - resp.header = IAX2.Header.parse(data) - if ( not(resp.header) ) then return end + resp.header = IAX2.Header.parse(data) + if ( not(resp.header) ) then return end - local pos = 13 - resp.ies = {} - repeat - local ie = {} - pos, ie.type, ie.value = bin.unpack(">Cp", data, pos) - table.insert(resp.ies, ie) - until( pos > #data ) - return resp - end, + local pos = 13 + resp.ies = {} + repeat + local ie = {} + pos, ie.type, ie.value = bin.unpack(">Cp", data, pos) + table.insert(resp.ies, ie) + until( pos > #data ) + return resp + end, - } + } } Helper = { - -- Creates a new Helper instance - -- @param host table as received by the action method - -- @param port table as received by the action method - -- @param options table containing helper options, currently - -- timeout socket timeout in ms - -- @return o instance of Helper - new = function(self, host, port, options) - local o = { host = host, port = port, options = options or {} } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Helper instance + -- @param host table as received by the action method + -- @param port table as received by the action method + -- @param options table containing helper options, currently + -- timeout socket timeout in ms + -- @return o instance of Helper + new = function(self, host, port, options) + local o = { host = host, port = port, options = options or {} } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects the UDP socket to the server - -- @return status true on success, false on failure - -- @return err message containing error if status is false - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(self.options.timeout or 5000) - return self.socket:connect(self.host, self.port) - end, + -- Connects the UDP socket to the server + -- @return status true on success, false on failure + -- @return err message containing error if status is false + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(self.options.timeout or 5000) + return self.socket:connect(self.host, self.port) + end, - -- Sends a request to the server and receives the response - -- @param req instance containing the request to send to the server - -- @return status true on success, false on failure - -- @return resp instance of response on success, - -- err containing the error message on failure - exch = function(self, req) - local status, err = self.socket:send(tostring(req)) - if ( not(status) ) then - return false, "Failed to send request to server" - end - local status, data = self.socket:receive() - if ( not(status) ) then - return false, "Failed to receive response from server" - end + -- Sends a request to the server and receives the response + -- @param req instance containing the request to send to the server + -- @return status true on success, false on failure + -- @return resp instance of response on success, + -- err containing the error message on failure + exch = function(self, req) + local status, err = self.socket:send(tostring(req)) + if ( not(status) ) then + return false, "Failed to send request to server" + end + local status, data = self.socket:receive() + if ( not(status) ) then + return false, "Failed to receive response from server" + end - local resp = IAX2.Response.parse(data) - return true, resp - end, + local resp = IAX2.Response.parse(data) + return true, resp + end, - -- Request a session release - -- @param username string containing the extention (username) - -- @param password string containing the password - regRelease = function(self, username, password) + -- Request a session release + -- @param username string containing the extention (username) + -- @param password string containing the password + regRelease = function(self, username, password) - local src_call = math.random(32767) - local header = IAX2.Header:new(src_call, 0, os.time(), 0, 0, IAX2.FrameType.IAX, IAX2.SubClass.REGREL) - local regrel = IAX2.Request:new(header) + local src_call = math.random(32767) + local header = IAX2.Header:new(src_call, 0, os.time(), 0, 0, IAX2.FrameType.IAX, IAX2.SubClass.REGREL) + local regrel = IAX2.Request:new(header) - regrel:setIE(IAX2.InfoElement.USERNAME, username) - regrel:setIE(IAX2.InfoElement.CALLTOKEN, "") + regrel:setIE(IAX2.InfoElement.USERNAME, username) + regrel:setIE(IAX2.InfoElement.CALLTOKEN, "") - local status, resp = self:exch(regrel) - if ( not(status) ) then - return false, resp - end + local status, resp = self:exch(regrel) + if ( not(status) ) then + return false, resp + end - if ( not(resp) or IAX2.SubClass.CALLTOKEN ~= resp.header.subclass ) then - return false, "Unexpected response" - end + if ( not(resp) or IAX2.SubClass.CALLTOKEN ~= resp.header.subclass ) then + return false, "Unexpected response" + end - local token = resp:getIE(IAX2.InfoElement.CALLTOKEN) - if ( not(token) ) then - return false, "Failed to get token" - end + local token = resp:getIE(IAX2.InfoElement.CALLTOKEN) + if ( not(token) ) then + return false, "Failed to get token" + end - regrel:setIE(IAX2.InfoElement.CALLTOKEN, token.value) - status, resp = self:exch(regrel) - if ( not(status) ) then - return false, resp - end + regrel:setIE(IAX2.InfoElement.CALLTOKEN, token.value) + status, resp = self:exch(regrel) + if ( not(status) ) then + return false, resp + end - local challenge = resp:getIE(IAX2.InfoElement.CHALLENGE) - if ( not(challenge) ) then - return false, "Failed to retrieve challenge from server" - end + local challenge = resp:getIE(IAX2.InfoElement.CHALLENGE) + if ( not(challenge) ) then + return false, "Failed to retrieve challenge from server" + end - regrel.header.iseqno = 1 - regrel.header.oseqno = 1 - regrel.header.dst_call = resp.header.src_call - regrel.ies = {} + regrel.header.iseqno = 1 + regrel.header.oseqno = 1 + regrel.header.dst_call = resp.header.src_call + regrel.ies = {} - local hash = stdnse.tohex(openssl.md5(challenge.value .. password)) - regrel:setIE(IAX2.InfoElement.USERNAME, username) - regrel:setIE(IAX2.InfoElement.MD5_RESULT, hash) + local hash = stdnse.tohex(openssl.md5(challenge.value .. password)) + regrel:setIE(IAX2.InfoElement.USERNAME, username) + regrel:setIE(IAX2.InfoElement.MD5_RESULT, hash) - status, resp = self:exch(regrel) - if ( not(status) ) then - return false, resp - end + status, resp = self:exch(regrel) + if ( not(status) ) then + return false, resp + end - if ( IAX2.SubClass.ACK == resp.header.subclass ) then - local data - status, data = self.socket:receive() - resp = IAX2.Response.parse(data) - end + if ( IAX2.SubClass.ACK == resp.header.subclass ) then + local data + status, data = self.socket:receive() + resp = IAX2.Response.parse(data) + end - if ( status and IAX2.SubClass.REGACK == resp.header.subclass ) then - return true - end - return false, "Release failed" - end, + if ( status and IAX2.SubClass.REGACK == resp.header.subclass ) then + return true + end + return false, "Release failed" + end, - -- Close the connection with the server - -- @return true on success, false on failure - close = function(self) - return self.socket:close() - end, + -- Close the connection with the server + -- @return true on success, false on failure + close = function(self) + return self.socket:close() + end, } diff --git a/nselib/imap.lua b/nselib/imap.lua index f75bccf38..37f3fbbee 100644 --- a/nselib/imap.lua +++ b/nselib/imap.lua @@ -10,19 +10,19 @@ -- -- The following example illustrates the reommended use of the library: -- --- local helper = imap.Helper:new(host, port) --- helper:connect() --- helper:login("user","password","PLAIN") --- helper:close() +-- local helper = imap.Helper:new(host, port) +-- helper:connect() +-- helper:login("user","password","PLAIN") +-- helper:close() -- -- -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html -- @author = "Brandon Enright, Patrik Karlsson" -- Version 0.2 --- Revised 07/15/2011 - v0.2 - added the IMAP and Helper classes --- added support for LOGIN and AUTHENTICATE --- +-- Revised 07/15/2011 - v0.2 - added the IMAP and Helper classes +-- added support for LOGIN and AUTHENTICATE +-- local base64 = require "base64" local comm = require "comm" @@ -34,192 +34,192 @@ _ENV = stdnse.module("imap", stdnse.seeall) IMAP = { - --- Creates a new instance of the IMAP class - -- - -- @param host table as received by the script action method - -- @param port table as received by the script action method - -- @param options table containing options, currently - -- timeout - number containing the seconds to wait for - -- a response - new = function(self, host, port, options) - local o = { - host = host, - port = port, - counter = 1, - timeout = ( options and options.timeout ) or 10000 - } - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new instance of the IMAP class + -- + -- @param host table as received by the script action method + -- @param port table as received by the script action method + -- @param options table containing options, currently + -- timeout - number containing the seconds to wait for + -- a response + new = function(self, host, port, options) + local o = { + host = host, + port = port, + counter = 1, + timeout = ( options and options.timeout ) or 10000 + } + setmetatable(o, self) + self.__index = self + return o + end, - --- Receives a response from the IMAP server - -- - -- @return status true on success, false on failure - -- @return data string containing the received data - receive = function(self) - local data = "" - repeat - local status, tmp = self.socket:receive_buf("\r\n", false) - if( not(status) ) then return false, tmp end - data = data .. tmp - until( tmp:match(("^A%04d"):format(self.counter - 1)) or tmp:match("^%+")) + --- Receives a response from the IMAP server + -- + -- @return status true on success, false on failure + -- @return data string containing the received data + receive = function(self) + local data = "" + repeat + local status, tmp = self.socket:receive_buf("\r\n", false) + if( not(status) ) then return false, tmp end + data = data .. tmp + until( tmp:match(("^A%04d"):format(self.counter - 1)) or tmp:match("^%+")) - return true, data - end, + return true, data + end, - --- Sends a request to the IMAP server - -- - -- @param cmd string containing the command to send to the server eg. - -- eg. (AUTHENTICATE, LOGIN) - -- @param params string containing the command parameters - -- @return true on success, false on failure - -- @return err string containing the error if status was false - send = function(self, cmd, params) - local data - if ( not(params) ) then - data = ("A%04d %s\r\n"):format(self.counter, cmd) - else - data = ("A%04d %s %s\r\n"):format(self.counter, cmd, params) - end - local status, err = self.socket:send(data) - if ( not(status) ) then return false, err end - self.counter = self.counter + 1 - return true - end, + --- Sends a request to the IMAP server + -- + -- @param cmd string containing the command to send to the server eg. + -- eg. (AUTHENTICATE, LOGIN) + -- @param params string containing the command parameters + -- @return true on success, false on failure + -- @return err string containing the error if status was false + send = function(self, cmd, params) + local data + if ( not(params) ) then + data = ("A%04d %s\r\n"):format(self.counter, cmd) + else + data = ("A%04d %s %s\r\n"):format(self.counter, cmd, params) + end + local status, err = self.socket:send(data) + if ( not(status) ) then return false, err end + self.counter = self.counter + 1 + return true + end, - --- Connect to the server - -- - -- @return status true on success, false on failure - -- @return banner string containing the server banner - connect = function(self) - local socket, banner, opt = comm.tryssl( self.host, self.port, "", { recv_before = true } ) - if ( not(socket) ) then return false, "ERROR: Failed to connect to server" end - socket:set_timeout(self.timeout) - if ( not(socket) or not(banner) ) then return false, "ERROR: Failed to connect to server" end - self.socket = socket - return true, banner - end, + --- Connect to the server + -- + -- @return status true on success, false on failure + -- @return banner string containing the server banner + connect = function(self) + local socket, banner, opt = comm.tryssl( self.host, self.port, "", { recv_before = true } ) + if ( not(socket) ) then return false, "ERROR: Failed to connect to server" end + socket:set_timeout(self.timeout) + if ( not(socket) or not(banner) ) then return false, "ERROR: Failed to connect to server" end + self.socket = socket + return true, banner + end, - --- Authenticate to the server (non PLAIN text mode) - -- Currently supported algorithms are CRAM-MD5 and CRAM-SHA1 - -- - -- @param username string containing the username - -- @param pass string containing the password - -- @param mech string containing a authentication mechanism, currently - -- CRAM-MD5 or CRAM-SHA1 - -- @return status true if login was successful, false on failure - -- @return err string containing the error message if status was false - authenticate = function(self, username, pass, mech) - assert( mech == "NTLM" or - mech == "DIGEST-MD5" or - mech == "CRAM-MD5" or - mech == "PLAIN", - "Unsupported authentication mechanism") + --- Authenticate to the server (non PLAIN text mode) + -- Currently supported algorithms are CRAM-MD5 and CRAM-SHA1 + -- + -- @param username string containing the username + -- @param pass string containing the password + -- @param mech string containing a authentication mechanism, currently + -- CRAM-MD5 or CRAM-SHA1 + -- @return status true if login was successful, false on failure + -- @return err string containing the error message if status was false + authenticate = function(self, username, pass, mech) + assert( mech == "NTLM" or + mech == "DIGEST-MD5" or + mech == "CRAM-MD5" or + mech == "PLAIN", + "Unsupported authentication mechanism") - local status, err = self:send("AUTHENTICATE", mech) + local status, err = self:send("AUTHENTICATE", mech) - if( not(status) ) then return false, "ERROR: Failed to send data" end + if( not(status) ) then return false, "ERROR: Failed to send data" end - local status, data = self:receive() - if( not(status) ) then return false, "ERROR: Failed to receive challenge" end + local status, data = self:receive() + if( not(status) ) then return false, "ERROR: Failed to receive challenge" end - if ( mech == "NTLM" ) then - -- sniffed of the wire, seems to always be the same - -- decodes to some NTLMSSP blob greatness - status, data = self.socket:send("TlRMTVNTUAABAAAAB7IIogYABgA3AAAADwAPACgAAAAFASgKAAAAD0FCVVNFLUFJUi5MT0NBTERPTUFJTg==\r\n") - if ( not(status) ) then return false, "ERROR: Failed to send NTLM packet" end - status, data = self:receive() - if ( not(status) ) then return false, "ERROR: Failed to receieve NTLM challenge" end - end + if ( mech == "NTLM" ) then + -- sniffed of the wire, seems to always be the same + -- decodes to some NTLMSSP blob greatness + status, data = self.socket:send("TlRMTVNTUAABAAAAB7IIogYABgA3AAAADwAPACgAAAAFASgKAAAAD0FCVVNFLUFJUi5MT0NBTERPTUFJTg==\r\n") + if ( not(status) ) then return false, "ERROR: Failed to send NTLM packet" end + status, data = self:receive() + if ( not(status) ) then return false, "ERROR: Failed to receieve NTLM challenge" end + end - if ( data:match(("^A%04d "):format(self.counter-1)) ) then - return false, "ERROR: Authentication mechanism not supported" - end + if ( data:match(("^A%04d "):format(self.counter-1)) ) then + return false, "ERROR: Authentication mechanism not supported" + end - local digest, auth_data - if ( not(data:match("^+")) ) then - return false, "ERROR: Failed to receive proper response from server" - end - data = base64.dec(data:match("^+ (.*)")) + local digest, auth_data + if ( not(data:match("^+")) ) then + return false, "ERROR: Failed to receive proper response from server" + end + data = base64.dec(data:match("^+ (.*)")) - -- All mechanisms expect username and pass - -- add the otheronce for those who need them - local mech_params = { username, pass, data, "imap" } - auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params)) - auth_data = base64.enc(auth_data) .. "\r\n" + -- All mechanisms expect username and pass + -- add the otheronce for those who need them + local mech_params = { username, pass, data, "imap" } + auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params)) + auth_data = base64.enc(auth_data) .. "\r\n" - status, data = self.socket:send(auth_data) - if( not(status) ) then return false, "ERROR: Failed to send data" end + status, data = self.socket:send(auth_data) + if( not(status) ) then return false, "ERROR: Failed to send data" end - status, data = self:receive() - if( not(status) ) then return false, "ERROR: Failed to receive data" end + status, data = self:receive() + if( not(status) ) then return false, "ERROR: Failed to receive data" end - if ( mech == "DIGEST-MD5" ) then - local rspauth = data:match("^+ (.*)") - if ( rspauth ) then - rspauth = base64.dec(rspauth) - status, data = self.socket:send("\r\n") - status, data = self:receive() - end - end - if ( data:match(("^A%04d OK"):format(self.counter - 1)) ) then - return true - end - return false, "Login failed" - end, + if ( mech == "DIGEST-MD5" ) then + local rspauth = data:match("^+ (.*)") + if ( rspauth ) then + rspauth = base64.dec(rspauth) + status, data = self.socket:send("\r\n") + status, data = self:receive() + end + end + if ( data:match(("^A%04d OK"):format(self.counter - 1)) ) then + return true + end + return false, "Login failed" + end, - --- Login to the server using PLAIN text authentication - -- - -- @param username string containing the username - -- @param password string containing the password - -- @return status true on success, false on failure - -- @return err string containing the error message if status was false - login = function(self, username, password) - local status, err = self:send("LOGIN", ("\"%s\" \"%s\""):format(username, password)) - if( not(status) ) then return false, "ERROR: Failed to send data" end + --- Login to the server using PLAIN text authentication + -- + -- @param username string containing the username + -- @param password string containing the password + -- @return status true on success, false on failure + -- @return err string containing the error message if status was false + login = function(self, username, password) + local status, err = self:send("LOGIN", ("\"%s\" \"%s\""):format(username, password)) + if( not(status) ) then return false, "ERROR: Failed to send data" end - local status, data = self:receive() - if( not(status) ) then return false, "ERROR: Failed to receive data" end + local status, data = self:receive() + if( not(status) ) then return false, "ERROR: Failed to receive data" end - if ( data:match(("^A%04d OK"):format(self.counter - 1)) ) then - return true - end - return false, "Login failed" - end, + if ( data:match(("^A%04d OK"):format(self.counter - 1)) ) then + return true + end + return false, "Login failed" + end, - --- Retrieves a list of server capabilities (eg. supported authentication - -- mechanisms, QUOTA, UIDPLUS, ACL ...) - -- - -- @return status true on success, false on failure - -- @return capas array containing the capabilities that are supported - capabilities = function(self) - local capas = {} - local proto = (self.port.version and self.port.version.service_tunnel == "ssl" and "ssl") or "tcp" - local status, err = self:send("CAPABILITY") - if( not(status) ) then return false, err end + --- Retrieves a list of server capabilities (eg. supported authentication + -- mechanisms, QUOTA, UIDPLUS, ACL ...) + -- + -- @return status true on success, false on failure + -- @return capas array containing the capabilities that are supported + capabilities = function(self) + local capas = {} + local proto = (self.port.version and self.port.version.service_tunnel == "ssl" and "ssl") or "tcp" + local status, err = self:send("CAPABILITY") + if( not(status) ) then return false, err end - local status, line = self:receive() - if (not(status)) then - capas.CAPABILITY = false - else - while status do - if ( line:match("^%*%s+CAPABILITY") ) then - line = line:gsub("^%*%s+CAPABILITY", "") - for capability in line:gmatch("[%w%+=-]+") do - capas[capability] = true - end - break - end - status, line = self.socket:receive() - end - end - return true, capas - end, + local status, line = self:receive() + if (not(status)) then + capas.CAPABILITY = false + else + while status do + if ( line:match("^%*%s+CAPABILITY") ) then + line = line:gsub("^%*%s+CAPABILITY", "") + for capability in line:gmatch("[%w%+=-]+") do + capas[capability] = true + end + break + end + status, line = self.socket:receive() + end + end + return true, capas + end, - --- Closes the connection to the IMAP server - -- @return true on success, false on failure - close = function(self) return self.socket:close() end + --- Closes the connection to the IMAP server + -- @return true on success, false on failure + close = function(self) return self.socket:close() end } @@ -227,53 +227,53 @@ IMAP = { -- The helper class, that servers as interface to script writers Helper = { - -- @param host table as received by the script action method - -- @param port table as received by the script action method - -- @param options table containing options, currently - -- timeout - number containing the seconds to wait for - -- a response - new = function(self, host, port, options) - local o = { client = IMAP:new( host, port, options ) } - setmetatable(o, self) - self.__index = self - return o - end, + -- @param host table as received by the script action method + -- @param port table as received by the script action method + -- @param options table containing options, currently + -- timeout - number containing the seconds to wait for + -- a response + new = function(self, host, port, options) + local o = { client = IMAP:new( host, port, options ) } + setmetatable(o, self) + self.__index = self + return o + end, - --- Connects to the IMAP server - -- @return status true on success, false on failure - connect = function(self) - return self.client:connect() - end, + --- Connects to the IMAP server + -- @return status true on success, false on failure + connect = function(self) + return self.client:connect() + end, - --- Login to the server using eithe plain-text or using the authentication - -- mechanism provided in the mech argument. - -- - -- @param username string containing the username - -- @param password string containing the password - -- @param mech [optional] containing the authentication mechanism to use - -- @return status true on success, false on failure - login = function(self, username, password, mech) - if ( not(mech) or mech == "LOGIN" ) then - return self.client:login(username, password) - else - return self.client:authenticate(username, password, mech) - end - end, + --- Login to the server using eithe plain-text or using the authentication + -- mechanism provided in the mech argument. + -- + -- @param username string containing the username + -- @param password string containing the password + -- @param mech [optional] containing the authentication mechanism to use + -- @return status true on success, false on failure + login = function(self, username, password, mech) + if ( not(mech) or mech == "LOGIN" ) then + return self.client:login(username, password) + else + return self.client:authenticate(username, password, mech) + end + end, - --- Retrieves a list of server capabilities (eg. supported authentication - -- mechanisms, QUOTA, UIDPLUS, ACL ...) - -- - -- @return status true on success, false on failure - -- @return capas array containing the capabilities that are supported - capabilities = function(self) - return self.client:capabilities() - end, + --- Retrieves a list of server capabilities (eg. supported authentication + -- mechanisms, QUOTA, UIDPLUS, ACL ...) + -- + -- @return status true on success, false on failure + -- @return capas array containing the capabilities that are supported + capabilities = function(self) + return self.client:capabilities() + end, - --- Closes the connection to the IMAP server - -- @return true on success, false on failure - close = function(self) - return self.client:close() - end, + --- Closes the connection to the IMAP server + -- @return true on success, false on failure + close = function(self) + return self.client:close() + end, } diff --git a/nselib/ipp.lua b/nselib/ipp.lua index d9682932c..296f6f190 100644 --- a/nselib/ipp.lua +++ b/nselib/ipp.lua @@ -18,424 +18,424 @@ _ENV = stdnse.module("ipp", stdnse.seeall) -- The IPP layer IPP = { - StatusCode = { - OK = 0, - }, + StatusCode = { + OK = 0, + }, - State = { - IPP_JOB_PENDING = 3, - IPP_JOB_HELD = 4, - IPP_JOB_PROCESSING = 5, - IPP_JOB_STOPPED = 6, - IPP_JOB_CANCELED = 7, - IPP_JOB_ABORTED = 8, - IPP_JOB_COMPLETED = 9, - }, + State = { + IPP_JOB_PENDING = 3, + IPP_JOB_HELD = 4, + IPP_JOB_PROCESSING = 5, + IPP_JOB_STOPPED = 6, + IPP_JOB_CANCELED = 7, + IPP_JOB_ABORTED = 8, + IPP_JOB_COMPLETED = 9, + }, - StateName = { - [3] = "Pending", - [4] = "Held", - [5] = "Processing", - [6] = "Stopped", - [7] = "Canceled", - [8] = "Aborted", - [9] = "Completed", - }, + StateName = { + [3] = "Pending", + [4] = "Held", + [5] = "Processing", + [6] = "Stopped", + [7] = "Canceled", + [8] = "Aborted", + [9] = "Completed", + }, - OperationID = { - IPP_CANCEL_JOB = 0x0008, - IPP_GET_JOB_ATTRIBUTES = 0x0009, - IPP_GET_JOBS = 0x000a, - CUPS_GET_PRINTERS = 0x4002, - CUPS_GET_DOCUMENT = 0x4027 - }, + OperationID = { + IPP_CANCEL_JOB = 0x0008, + IPP_GET_JOB_ATTRIBUTES = 0x0009, + IPP_GET_JOBS = 0x000a, + CUPS_GET_PRINTERS = 0x4002, + CUPS_GET_DOCUMENT = 0x4027 + }, - PrinterState = { - IPP_PRINTER_IDLE = 3, - IPP_PRINTER_PROCESSING = 4, - IPP_PRINTER_STOPPED = 5, - }, + PrinterState = { + IPP_PRINTER_IDLE = 3, + IPP_PRINTER_PROCESSING = 4, + IPP_PRINTER_STOPPED = 5, + }, - Attribute = { + Attribute = { - IPP_TAG_OPERATION = 0x01, - IPP_TAG_JOB = 0x02, - IPP_TAG_END = 0x03, - IPP_TAG_PRINTER = 0x04, - IPP_TAG_INTEGER = 0x21, - IPP_TAG_ENUM = 0x23, - IPP_TAG_NAME = 0x42, - IPP_TAG_KEYWORD = 0x44, - IPP_TAG_URI = 0x45, - IPP_TAG_CHARSET = 0x47, - IPP_TAG_LANGUAGE = 0x48, + IPP_TAG_OPERATION = 0x01, + IPP_TAG_JOB = 0x02, + IPP_TAG_END = 0x03, + IPP_TAG_PRINTER = 0x04, + IPP_TAG_INTEGER = 0x21, + IPP_TAG_ENUM = 0x23, + IPP_TAG_NAME = 0x42, + IPP_TAG_KEYWORD = 0x44, + IPP_TAG_URI = 0x45, + IPP_TAG_CHARSET = 0x47, + IPP_TAG_LANGUAGE = 0x48, - new = function(self, tag, name, value) - local o = { tag = tag, name = name, value = value } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, tag, name, value) + local o = { tag = tag, name = name, value = value } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data, pos) - local attrib = IPP.Attribute:new() - local val - pos, attrib.tag, attrib.name, val = bin.unpack(">CPP", data, pos) - -- print(attrib.name, stdnse.tohex(val)) - attrib.value = {} - table.insert(attrib.value, { tag = attrib.tag, val = val }) + parse = function(data, pos) + local attrib = IPP.Attribute:new() + local val + pos, attrib.tag, attrib.name, val = bin.unpack(">CPP", data, pos) + -- print(attrib.name, stdnse.tohex(val)) + attrib.value = {} + table.insert(attrib.value, { tag = attrib.tag, val = val }) - repeat - local tag, name_len, val + repeat + local tag, name_len, val - if ( #data < pos + 3 ) then - break - end + if ( #data < pos + 3 ) then + break + end - pos, tag, name_len = bin.unpack(">CS", data, pos) - if ( name_len == 0 ) then - pos, val = bin.unpack(">P", data, pos) - table.insert(attrib.value, { tag = tag, val = val }) - else - pos = pos - 3 - end - until( name_len ~= 0 ) + pos, tag, name_len = bin.unpack(">CS", data, pos) + if ( name_len == 0 ) then + pos, val = bin.unpack(">P", data, pos) + table.insert(attrib.value, { tag = tag, val = val }) + else + pos = pos - 3 + end + until( name_len ~= 0 ) - -- do minimal decoding - for i=1, #attrib.value do - if ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_INTEGER ) then - attrib.value[i].val = select(2, bin.unpack(">I", attrib.value[i].val)) - elseif ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_ENUM ) then - attrib.value[i].val = select(2, bin.unpack(">I", attrib.value[i].val)) - end - end + -- do minimal decoding + for i=1, #attrib.value do + if ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_INTEGER ) then + attrib.value[i].val = select(2, bin.unpack(">I", attrib.value[i].val)) + elseif ( attrib.value[i].tag == IPP.Attribute.IPP_TAG_ENUM ) then + attrib.value[i].val = select(2, bin.unpack(">I", attrib.value[i].val)) + end + end - if ( 1 == #attrib.value ) then - attrib.value = attrib.value[1].val - end - --print(attrib.name, attrib.value, stdnse.tohex(val)) + if ( 1 == #attrib.value ) then + attrib.value = attrib.value[1].val + end + --print(attrib.name, attrib.value, stdnse.tohex(val)) - return pos, attrib - end, + return pos, attrib + end, - __tostring = function(self) - if ( "string" == type(self.value) ) then - return bin.pack(">CSASA", self.tag, #self.name, self.name, #self.value, self.value) - else - local data = bin.pack(">CSASA", self.tag, #self.name, self.name, #self.value[1].val, self.value[1].val) - for i=2, #self.value do - data = data .. bin.pack(">CSP", self.value[i].tag, 0, self.value[i].val) - end - return data - end - end + __tostring = function(self) + if ( "string" == type(self.value) ) then + return bin.pack(">CSASA", self.tag, #self.name, self.name, #self.value, self.value) + else + local data = bin.pack(">CSASA", self.tag, #self.name, self.name, #self.value[1].val, self.value[1].val) + for i=2, #self.value do + data = data .. bin.pack(">CSP", self.value[i].tag, 0, self.value[i].val) + end + return data + end + end - }, + }, - -- An attribute group, groups several attributes - AttributeGroup = { + -- An attribute group, groups several attributes + AttributeGroup = { - new = function(self, tag, attribs) - local o = { tag = tag, attribs = attribs or {} } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, tag, attribs) + local o = { tag = tag, attribs = attribs or {} } + setmetatable(o, self) + self.__index = self + return o + end, - addAttribute = function(self, attrib) - table.insert(self.attribs, attrib) - end, + addAttribute = function(self, attrib) + table.insert(self.attribs, attrib) + end, - -- - -- Gets the first attribute matching name and optionally tag from the - -- attribute group. - -- - -- @param name string containing the attribute name - -- @param tag number containing the attribute tag - getAttribute = function(self, name, tag) - for _, attrib in ipairs(self.attribs) do - if ( attrib.name == name ) then - if ( not(tag) ) then - return attrib - elseif ( tag and attrib.tag == tag ) then - return attrib - end - end - end - end, + -- + -- Gets the first attribute matching name and optionally tag from the + -- attribute group. + -- + -- @param name string containing the attribute name + -- @param tag number containing the attribute tag + getAttribute = function(self, name, tag) + for _, attrib in ipairs(self.attribs) do + if ( attrib.name == name ) then + if ( not(tag) ) then + return attrib + elseif ( tag and attrib.tag == tag ) then + return attrib + end + end + end + end, - getAttributeValue = function(self, name, tag) - for _, attrib in ipairs(self.attribs) do - if ( attrib.name == name ) then - if ( not(tag) ) then - return attrib.value - elseif ( tag and attrib.tag == tag ) then - return attrib.value - end - end - end - end, + getAttributeValue = function(self, name, tag) + for _, attrib in ipairs(self.attribs) do + if ( attrib.name == name ) then + if ( not(tag) ) then + return attrib.value + elseif ( tag and attrib.tag == tag ) then + return attrib.value + end + end + end + end, - __tostring = function(self) - local data = bin.pack("C", self.tag) + __tostring = function(self) + local data = bin.pack("C", self.tag) - for _, attrib in ipairs(self.attribs) do - data = data .. tostring(attrib) - end - return data - end + for _, attrib in ipairs(self.attribs) do + data = data .. tostring(attrib) + end + return data + end - }, + }, - -- The IPP request - Request = { + -- The IPP request + Request = { - new = function(self, opid, reqid) - local o = { - version = 0x0101, - opid = opid, - reqid = reqid, - attrib_groups = {}, - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, opid, reqid) + local o = { + version = 0x0101, + opid = opid, + reqid = reqid, + attrib_groups = {}, + } + setmetatable(o, self) + self.__index = self + return o + end, - addAttributeGroup = function(self, group) - table.insert( self.attrib_groups, group ) - end, + addAttributeGroup = function(self, group) + table.insert( self.attrib_groups, group ) + end, - __tostring = function(self) - local data = bin.pack(">SSI", self.version, self.opid, self.reqid ) + __tostring = function(self) + local data = bin.pack(">SSI", self.version, self.opid, self.reqid ) - for _, group in ipairs(self.attrib_groups) do - data = data .. tostring(group) - end - data = data .. bin.pack("C", IPP.Attribute.IPP_TAG_END) - return data - end, + for _, group in ipairs(self.attrib_groups) do + data = data .. tostring(group) + end + data = data .. bin.pack("C", IPP.Attribute.IPP_TAG_END) + return data + end, - }, + }, - -- A class to handle responses from the server - Response = { + -- A class to handle responses from the server + Response = { - -- Creates a new instance of response - new = function(self) - local o = {} - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of response + new = function(self) + local o = {} + setmetatable(o, self) + self.__index = self + return o + end, - getAttributeGroups = function(self, tag) - local groups = {} - for _, v in ipairs(self.attrib_groups or {}) do - if ( v.tag == tag ) then - table.insert(groups, v) - end - end - return groups - end, + getAttributeGroups = function(self, tag) + local groups = {} + for _, v in ipairs(self.attrib_groups or {}) do + if ( v.tag == tag ) then + table.insert(groups, v) + end + end + return groups + end, - parse = function(data) - local resp = IPP.Response:new() - local pos + parse = function(data) + local resp = IPP.Response:new() + local pos - pos, resp.version, resp.status, resp.reqid = bin.unpack(">SSI", data) + pos, resp.version, resp.status, resp.reqid = bin.unpack(">SSI", data) - resp.attrib_groups = {} - local group - repeat - local tag, attrib - pos, tag = bin.unpack(">C", data, pos) + resp.attrib_groups = {} + local group + repeat + local tag, attrib + pos, tag = bin.unpack(">C", data, pos) - if ( tag == IPP.Attribute.IPP_TAG_OPERATION or - tag == IPP.Attribute.IPP_TAG_JOB or - tag == IPP.Attribute.IPP_TAG_PRINTER or - tag == IPP.Attribute.IPP_TAG_END ) then + if ( tag == IPP.Attribute.IPP_TAG_OPERATION or + tag == IPP.Attribute.IPP_TAG_JOB or + tag == IPP.Attribute.IPP_TAG_PRINTER or + tag == IPP.Attribute.IPP_TAG_END ) then - if ( group ) then - table.insert(resp.attrib_groups, group) - group = IPP.AttributeGroup:new(tag) - else - group = IPP.AttributeGroup:new(tag) - end - else - pos = pos - 1 - end + if ( group ) then + table.insert(resp.attrib_groups, group) + group = IPP.AttributeGroup:new(tag) + else + group = IPP.AttributeGroup:new(tag) + end + else + pos = pos - 1 + end - if ( not(group) ) then - stdnse.print_debug(2, "Unexpected tag: %d", tag) - return - end + if ( not(group) ) then + stdnse.print_debug(2, "Unexpected tag: %d", tag) + return + end - pos, attrib = IPP.Attribute.parse(data, pos) - group:addAttribute(attrib) + pos, attrib = IPP.Attribute.parse(data, pos) + group:addAttribute(attrib) - until( pos == #data + 1) + until( pos == #data + 1) - return resp - end, + return resp + end, - }, + }, } HTTP = { - Request = function(host, port, request) - local headers = { - ['Content-Type'] = 'application/ipp', - ['User-Agent'] = 'CUPS/1.5.1', - } - port.version.service_tunnel = "ssl" - local http_resp = http.post(host, port, '/', { header = headers }, nil, tostring(request)) - if ( http_resp.status ~= 200 ) then - return false, "Unexpected response from server" - end + Request = function(host, port, request) + local headers = { + ['Content-Type'] = 'application/ipp', + ['User-Agent'] = 'CUPS/1.5.1', + } + port.version.service_tunnel = "ssl" + local http_resp = http.post(host, port, '/', { header = headers }, nil, tostring(request)) + if ( http_resp.status ~= 200 ) then + return false, "Unexpected response from server" + end - local response = IPP.Response.parse(http_resp.body) - if ( not(response) ) then - return false, "Failed to parse response" - end + local response = IPP.Response.parse(http_resp.body) + if ( not(response) ) then + return false, "Failed to parse response" + end - return true, response - end, + return true, response + end, } Helper = { - new = function(self, host, port, options) - local o = { host = host, port = port, options = options or {} } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, host, port, options) + local o = { host = host, port = port, options = options or {} } + setmetatable(o, self) + self.__index = self + return o + end, - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(self.options.timeout or 10000) - return self.socket:connect(self.host, self.port) - end, + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(self.options.timeout or 10000) + return self.socket:connect(self.host, self.port) + end, - getPrinters = function(self) + getPrinters = function(self) - local attribs = { - IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ), - IPP.Attribute:new(IPP.Attribute.IPP_TAG_LANGUAGE, "attributes-natural-language", "en"), - } + local attribs = { + IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ), + IPP.Attribute:new(IPP.Attribute.IPP_TAG_LANGUAGE, "attributes-natural-language", "en"), + } - local ag = IPP.AttributeGroup:new(IPP.Attribute.IPP_TAG_OPERATION, attribs) - local request = IPP.Request:new(IPP.OperationID.CUPS_GET_PRINTERS, 1) - request:addAttributeGroup(ag) + local ag = IPP.AttributeGroup:new(IPP.Attribute.IPP_TAG_OPERATION, attribs) + local request = IPP.Request:new(IPP.OperationID.CUPS_GET_PRINTERS, 1) + request:addAttributeGroup(ag) - local status, response = HTTP.Request( self.host, self.port, tostring(request) ) - if ( not(response) ) then - return status, response - end + local status, response = HTTP.Request( self.host, self.port, tostring(request) ) + if ( not(response) ) then + return status, response + end - local printers = {} + local printers = {} - for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_PRINTER)) do - local attrib = { - ["printer-name"] = "name", - ["printer-location"] = "location", - ["printer-make-and-model"] = "model", - ["printer-state"] = "state", - ["queued-job-count"] = "queue_count", - ["printer-dns-sd-name"] = "dns_sd_name", - } + for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_PRINTER)) do + local attrib = { + ["printer-name"] = "name", + ["printer-location"] = "location", + ["printer-make-and-model"] = "model", + ["printer-state"] = "state", + ["queued-job-count"] = "queue_count", + ["printer-dns-sd-name"] = "dns_sd_name", + } - local printer = {} - for k, v in pairs(attrib) do - if ( ag:getAttributeValue(k) ) then - printer[v] = ag:getAttributeValue(k) - end - end - table.insert(printers, printer) - end - return true, printers - end, + local printer = {} + for k, v in pairs(attrib) do + if ( ag:getAttributeValue(k) ) then + printer[v] = ag:getAttributeValue(k) + end + end + table.insert(printers, printer) + end + return true, printers + end, - getQueueInfo = function(self, uri) - local uri = uri or ("ipp://%s/"):format(self.host.ip) + getQueueInfo = function(self, uri) + local uri = uri or ("ipp://%s/"):format(self.host.ip) - local attribs = { - IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ), - IPP.Attribute:new(IPP.Attribute.IPP_TAG_LANGUAGE, "attributes-natural-language", "en-us"), - IPP.Attribute:new(IPP.Attribute.IPP_TAG_URI, "printer-uri", uri), - IPP.Attribute:new(IPP.Attribute.IPP_TAG_KEYWORD, "requested-attributes", { - -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-host-name"}, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "com.apple.print.JobInfo.PMJobName"}, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "com.apple.print.JobInfo.PMJobOwner"}, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-id" }, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-k-octets" }, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-name" }, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-state" }, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "printer-uri" }, - -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-user-name" }, - -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-state-message" }, - -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-uri" }, - { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "time-at-creation" } } ), - IPP.Attribute:new(IPP.Attribute.IPP_TAG_KEYWORD, "which-jobs", "not-completed" ) - } + local attribs = { + IPP.Attribute:new(IPP.Attribute.IPP_TAG_CHARSET, "attributes-charset", "utf-8" ), + IPP.Attribute:new(IPP.Attribute.IPP_TAG_LANGUAGE, "attributes-natural-language", "en-us"), + IPP.Attribute:new(IPP.Attribute.IPP_TAG_URI, "printer-uri", uri), + IPP.Attribute:new(IPP.Attribute.IPP_TAG_KEYWORD, "requested-attributes", { + -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-host-name"}, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "com.apple.print.JobInfo.PMJobName"}, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "com.apple.print.JobInfo.PMJobOwner"}, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-id" }, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-k-octets" }, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-name" }, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-state" }, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "printer-uri" }, + -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-originating-user-name" }, + -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-state-message" }, + -- { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "job-printer-uri" }, + { tag = IPP.Attribute.IPP_TAG_KEYWORD, val = "time-at-creation" } } ), + IPP.Attribute:new(IPP.Attribute.IPP_TAG_KEYWORD, "which-jobs", "not-completed" ) + } - local ag = IPP.AttributeGroup:new(IPP.Attribute.IPP_TAG_OPERATION, attribs) - local request = IPP.Request:new(IPP.OperationID.IPP_GET_JOBS, 1) - request:addAttributeGroup(ag) + local ag = IPP.AttributeGroup:new(IPP.Attribute.IPP_TAG_OPERATION, attribs) + local request = IPP.Request:new(IPP.OperationID.IPP_GET_JOBS, 1) + request:addAttributeGroup(ag) - local status, response = HTTP.Request( self.host, self.port, tostring(request) ) - if ( not(response) ) then - return status, response - end + local status, response = HTTP.Request( self.host, self.port, tostring(request) ) + if ( not(response) ) then + return status, response + end - local results = {} - for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_JOB)) do - local uri = ag:getAttributeValue("printer-uri") - local printer = uri:match(".*/(.*)$") or "Unknown" - -- some jobs have mutlitple state attributes, so far the ENUM ones have been correct - local state = ag:getAttributeValue("job-state", IPP.Attribute.IPP_TAG_ENUM) or ag:getAttributeValue("job-state") - -- some jobs have multiple id tag, so far the INTEGER type have shown the correct ID - local id = ag:getAttributeValue("job-id", IPP.Attribute.IPP_TAG_INTEGER) or ag:getAttributeValue("job-id") - local attr = ag:getAttribute("time-at-creation") - local tm = ag:getAttributeValue("time-at-creation") - local size = ag:getAttributeValue("job-k-octets") .. "k" - local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName") or "Unknown" - local owner = ag:getAttributeValue("com.apple.print.JobInfo.PMJobOwner") or "Unknown" + local results = {} + for _, ag in ipairs(response:getAttributeGroups(IPP.Attribute.IPP_TAG_JOB)) do + local uri = ag:getAttributeValue("printer-uri") + local printer = uri:match(".*/(.*)$") or "Unknown" + -- some jobs have mutlitple state attributes, so far the ENUM ones have been correct + local state = ag:getAttributeValue("job-state", IPP.Attribute.IPP_TAG_ENUM) or ag:getAttributeValue("job-state") + -- some jobs have multiple id tag, so far the INTEGER type have shown the correct ID + local id = ag:getAttributeValue("job-id", IPP.Attribute.IPP_TAG_INTEGER) or ag:getAttributeValue("job-id") + local attr = ag:getAttribute("time-at-creation") + local tm = ag:getAttributeValue("time-at-creation") + local size = ag:getAttributeValue("job-k-octets") .. "k" + local jobname = ag:getAttributeValue("com.apple.print.JobInfo.PMJobName") or "Unknown" + local owner = ag:getAttributeValue("com.apple.print.JobInfo.PMJobOwner") or "Unknown" - results[printer] = results[printer] or {} - table.insert(results[printer], { - id = id, - time = os.date("%Y-%m-%d %H:%M:%S", tm), - state = ( IPP.StateName[tonumber(state)] or "Unknown" ), - size = size, - owner = owner, - jobname = jobname }) - end + results[printer] = results[printer] or {} + table.insert(results[printer], { + id = id, + time = os.date("%Y-%m-%d %H:%M:%S", tm), + state = ( IPP.StateName[tonumber(state)] or "Unknown" ), + size = size, + owner = owner, + jobname = jobname }) + end - local output = {} - for name, entries in pairs(results) do - local t = tab.new(5) - tab.addrow(t, "id", "time", "state", "size (kb)", "owner", "jobname") - for _, entry in ipairs(entries) do - tab.addrow(t, entry.id, entry.time, entry.state, entry.size, entry.owner, entry.jobname) - end - if ( 1<#t ) then - table.insert(output, { name = name, tab.dump(t) }) - end - end + local output = {} + for name, entries in pairs(results) do + local t = tab.new(5) + tab.addrow(t, "id", "time", "state", "size (kb)", "owner", "jobname") + for _, entry in ipairs(entries) do + tab.addrow(t, entry.id, entry.time, entry.state, entry.size, entry.owner, entry.jobname) + end + if ( 1<#t ) then + table.insert(output, { name = name, tab.dump(t) }) + end + end - return output - end, + return output + end, - close = function(self) - return self.socket:close() - end, + close = function(self) + return self.socket:close() + end, } return _ENV; diff --git a/nselib/isns.lua b/nselib/isns.lua index 8095cb0a7..00fb12fc0 100644 --- a/nselib/isns.lua +++ b/nselib/isns.lua @@ -16,550 +16,550 @@ _ENV = stdnse.module("isns", stdnse.seeall); iSCSI = { - NodeType = { - TARGET = 1, - INITIATOR = 2, - CONTROL = 4, - } + NodeType = { + TARGET = 1, + INITIATOR = 2, + CONTROL = 4, + } } Header = { - VERSION = 1, + VERSION = 1, - -- - -- Creates a header instance - -- - -- @param func_id number containing the function ID of the message - -- @param pdu_len number containing the length of the PDU - -- @param flags number containing the message flags - -- @param trans_id number containing the transaction id - -- @param seq_id number containing the sequence id - -- @return o new class instance - new = function(self, func_id, pdu_len, flags, trans_id, seq_id) - local o = { - ver = Header.VERSION, - func_id = func_id, - flags = flags, - trans_id = trans_id, - seq_id = seq_id, - pdu_len = pdu_len, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a header instance + -- + -- @param func_id number containing the function ID of the message + -- @param pdu_len number containing the length of the PDU + -- @param flags number containing the message flags + -- @param trans_id number containing the transaction id + -- @param seq_id number containing the sequence id + -- @return o new class instance + new = function(self, func_id, pdu_len, flags, trans_id, seq_id) + local o = { + ver = Header.VERSION, + func_id = func_id, + flags = flags, + trans_id = trans_id, + seq_id = seq_id, + pdu_len = pdu_len, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Parses a opaque string and creates a new Header instance - -- - -- @param data opaques string containing the raw data - -- @return hdr new instance of Header - parse = function(data) - local hdr = Header:new() - local pos + -- + -- Parses a opaque string and creates a new Header instance + -- + -- @param data opaques string containing the raw data + -- @return hdr new instance of Header + parse = function(data) + local hdr = Header:new() + local pos - pos, hdr.ver, hdr.func_id, hdr.pdu_len, hdr.flags, hdr.trans_id, - hdr.seq_id = bin.unpack(">SSSSSS", data) + pos, hdr.ver, hdr.func_id, hdr.pdu_len, hdr.flags, hdr.trans_id, + hdr.seq_id = bin.unpack(">SSSSSS", data) - return hdr - end, + return hdr + end, - -- - -- Converts the instance to an opaque string - -- @return str containing an opaque string - __tostring = function(self) - return bin.pack(">SSSSSS", self.ver, self.func_id, - self.pdu_len, self.flags, self.trans_id, self.seq_id ) - end + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + return bin.pack(">SSSSSS", self.ver, self.func_id, + self.pdu_len, self.flags, self.trans_id, self.seq_id ) + end } Attribute = { - Tag = { - ISNS_TAG_DELIMITER = 0, - ISNS_TAG_ENTITY_IDENTIFIER = 1, - ISNS_TAG_ENTITY_PROTOCOL = 2, - ISNS_TAG_MGMT_IP_ADDRESS = 3, - ISNS_TAG_TIMESTAMP = 4, - ISNS_TAG_PROTOCOL_VERSION_RANGE = 5, - ISNS_TAG_REGISTRATION_PERIOD = 6, - ISNS_TAG_ENTITY_INDEX = 7, - ISNS_TAG_ENTITY_NEXT_INDEX = 8, - ISNS_TAG_ENTITY_ISAKMP_PHASE_1 = 11, - ISNS_TAG_ENTITY_CERTIFICATE = 12, - ISNS_TAG_PORTAL_IP_ADDRESS = 16, - ISNS_TAG_PORTAL_TCP_UDP_PORT = 17, - ISNS_TAG_PORTAL_SYMBOLIC_NAME = 18, - ISNS_TAG_ESI_INTERVAL = 19, - ISNS_TAG_ESI_PORT = 20, - ISNS_TAG_PORTAL_INDEX = 22, - ISNS_TAG_SCN_PORT = 23, - ISNS_TAG_PORTAL_NEXT_INDEX = 24, - ISNS_TAG_PORTAL_SECURITY_BITMAP = 27, - ISNS_TAG_PORTAL_ISAKMP_PHASE_1 = 28, - ISNS_TAG_PORTAL_ISAKMP_PHASE_2 = 29, - ISNS_TAG_PORTAL_CERTIFICATE = 31, - ISNS_TAG_ISCSI_NAME = 32, - ISNS_TAG_ISCSI_NODE_TYPE = 33, - ISNS_TAG_ISCSI_ALIAS = 34, - ISNS_TAG_ISCSI_SCN_BITMAP = 35, - ISNS_TAG_ISCSI_NODE_INDEX = 36, - ISNS_TAG_WWNN_TOKEN = 37, - ISNS_TAG_ISCSI_NODE_NEXT_INDEX = 38, - ISNS_TAG_ISCSI_AUTHMETHOD = 42, - ISNS_TAG_PG_ISCSI_NAME = 48, - ISNS_TAG_PG_PORTAL_IP_ADDR = 49, - ISNS_TAG_PG_PORTAL_TCP_UDP_PORT = 50, - ISNS_TAG_PG_TAG = 51, - ISNS_TAG_PG_INDEX = 52, - ISNS_TAG_PG_NEXT_INDEX = 53, - ISNS_TAG_FC_PORT_NAME_WWPN = 64, - ISNS_TAG_PORT_ID = 65, - ISNS_TAG_FC_PORT_TYPE = 66, - ISNS_TAG_SYMBOLIC_PORT_NAME = 67, - ISNS_TAG_FABRIC_PORT_NAME = 68, - ISNS_TAG_HARD_ADDRESS = 69, - ISNS_TAG_PORT_IP_ADDRESS = 70, - ISNS_TAG_CLASS_OF_SERVICE = 71, - ISNS_TAG_FC4_TYPES = 72, - ISNS_TAG_FC4_DESCRIPTOR = 73, - ISNS_TAG_FC4_FEATURES = 74, - ISNS_TAG_IFCP_SCN_BITMAP = 75, - ISNS_TAG_PORT_ROLE = 76, - ISNS_TAG_PERMANENT_PORT_NAME = 77, - ISNS_TAG_FC4_TYPE_CODE = 95, - ISNS_TAG_FC_NODE_NAME_WWNN = 96, - ISNS_TAG_SYMBOLIC_NODE_NAME = 97, - ISNS_TAG_NODE_IP_ADDRESS = 98, - ISNS_TAG_NODE_IPA = 99, - ISNS_TAG_PROXY_ISCSI_NAME = 101, - ISNS_TAG_SWITCH_NAME = 128, - ISNS_TAG_PREFERRED_ID = 129, - ISNS_TAG_ASSIGNED_ID = 130, - ISNS_TAG_VIRTUAL_FABRIC_ID = 131, - ISNS_TAG_SERVER_VENDOR_OUI = 256, - ISNS_TAG_DD_SET_ID = 2049, - ISNS_TAG_DD_SET_SYMBOLIC_NAME = 2050, - ISNS_TAG_DD_SET_STATUS = 2051, - ISNS_TAG_DD_SET_NEXT_ID = 2052, - ISNS_TAG_DD_ID = 2065, - ISNS_TAG_DD_SYMBOLIC_NAME = 2066, - ISNS_TAG_DD_MEMBER_ISCSI_INDEX = 2067, - ISNS_TAG_DD_MEMBER_ISCSI_NAME = 2068, - ISNS_TAG_DD_MEMBER_FC_PORT_NAME = 2069, - ISNS_TAG_DD_MEMBER_PORTAL_INDEX = 2070, - ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR = 2071, - ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT = 2072, - ISNS_TAG_DD_FEATURES = 2078, - ISNS_TAG_DD_NEXT_ID = 2079, - ISNS_VENDOR_SPECIFIC_SERVER_BASE = 257, - ISNS_VENDOR_SPECIFIC_ENTITY_BASE = 385, - ISNS_VENDOR_SPECIFIC_PORTAL_BASE = 513, - ISNS_VENDOR_SPECIFIC_NODE_BASE = 641, - ISNS_VENDOR_SPECIFIC_DD_BASE = 1024, - ISNS_VENDOR_SPECIFIC_DDSET_BASE = 1281, - ISNS_VENDOR_SPECIFIC_OTHER_BASE = 1537, - }, + Tag = { + ISNS_TAG_DELIMITER = 0, + ISNS_TAG_ENTITY_IDENTIFIER = 1, + ISNS_TAG_ENTITY_PROTOCOL = 2, + ISNS_TAG_MGMT_IP_ADDRESS = 3, + ISNS_TAG_TIMESTAMP = 4, + ISNS_TAG_PROTOCOL_VERSION_RANGE = 5, + ISNS_TAG_REGISTRATION_PERIOD = 6, + ISNS_TAG_ENTITY_INDEX = 7, + ISNS_TAG_ENTITY_NEXT_INDEX = 8, + ISNS_TAG_ENTITY_ISAKMP_PHASE_1 = 11, + ISNS_TAG_ENTITY_CERTIFICATE = 12, + ISNS_TAG_PORTAL_IP_ADDRESS = 16, + ISNS_TAG_PORTAL_TCP_UDP_PORT = 17, + ISNS_TAG_PORTAL_SYMBOLIC_NAME = 18, + ISNS_TAG_ESI_INTERVAL = 19, + ISNS_TAG_ESI_PORT = 20, + ISNS_TAG_PORTAL_INDEX = 22, + ISNS_TAG_SCN_PORT = 23, + ISNS_TAG_PORTAL_NEXT_INDEX = 24, + ISNS_TAG_PORTAL_SECURITY_BITMAP = 27, + ISNS_TAG_PORTAL_ISAKMP_PHASE_1 = 28, + ISNS_TAG_PORTAL_ISAKMP_PHASE_2 = 29, + ISNS_TAG_PORTAL_CERTIFICATE = 31, + ISNS_TAG_ISCSI_NAME = 32, + ISNS_TAG_ISCSI_NODE_TYPE = 33, + ISNS_TAG_ISCSI_ALIAS = 34, + ISNS_TAG_ISCSI_SCN_BITMAP = 35, + ISNS_TAG_ISCSI_NODE_INDEX = 36, + ISNS_TAG_WWNN_TOKEN = 37, + ISNS_TAG_ISCSI_NODE_NEXT_INDEX = 38, + ISNS_TAG_ISCSI_AUTHMETHOD = 42, + ISNS_TAG_PG_ISCSI_NAME = 48, + ISNS_TAG_PG_PORTAL_IP_ADDR = 49, + ISNS_TAG_PG_PORTAL_TCP_UDP_PORT = 50, + ISNS_TAG_PG_TAG = 51, + ISNS_TAG_PG_INDEX = 52, + ISNS_TAG_PG_NEXT_INDEX = 53, + ISNS_TAG_FC_PORT_NAME_WWPN = 64, + ISNS_TAG_PORT_ID = 65, + ISNS_TAG_FC_PORT_TYPE = 66, + ISNS_TAG_SYMBOLIC_PORT_NAME = 67, + ISNS_TAG_FABRIC_PORT_NAME = 68, + ISNS_TAG_HARD_ADDRESS = 69, + ISNS_TAG_PORT_IP_ADDRESS = 70, + ISNS_TAG_CLASS_OF_SERVICE = 71, + ISNS_TAG_FC4_TYPES = 72, + ISNS_TAG_FC4_DESCRIPTOR = 73, + ISNS_TAG_FC4_FEATURES = 74, + ISNS_TAG_IFCP_SCN_BITMAP = 75, + ISNS_TAG_PORT_ROLE = 76, + ISNS_TAG_PERMANENT_PORT_NAME = 77, + ISNS_TAG_FC4_TYPE_CODE = 95, + ISNS_TAG_FC_NODE_NAME_WWNN = 96, + ISNS_TAG_SYMBOLIC_NODE_NAME = 97, + ISNS_TAG_NODE_IP_ADDRESS = 98, + ISNS_TAG_NODE_IPA = 99, + ISNS_TAG_PROXY_ISCSI_NAME = 101, + ISNS_TAG_SWITCH_NAME = 128, + ISNS_TAG_PREFERRED_ID = 129, + ISNS_TAG_ASSIGNED_ID = 130, + ISNS_TAG_VIRTUAL_FABRIC_ID = 131, + ISNS_TAG_SERVER_VENDOR_OUI = 256, + ISNS_TAG_DD_SET_ID = 2049, + ISNS_TAG_DD_SET_SYMBOLIC_NAME = 2050, + ISNS_TAG_DD_SET_STATUS = 2051, + ISNS_TAG_DD_SET_NEXT_ID = 2052, + ISNS_TAG_DD_ID = 2065, + ISNS_TAG_DD_SYMBOLIC_NAME = 2066, + ISNS_TAG_DD_MEMBER_ISCSI_INDEX = 2067, + ISNS_TAG_DD_MEMBER_ISCSI_NAME = 2068, + ISNS_TAG_DD_MEMBER_FC_PORT_NAME = 2069, + ISNS_TAG_DD_MEMBER_PORTAL_INDEX = 2070, + ISNS_TAG_DD_MEMBER_PORTAL_IP_ADDR = 2071, + ISNS_TAG_DD_MEMBER_PORTAL_TCP_UDP_PORT = 2072, + ISNS_TAG_DD_FEATURES = 2078, + ISNS_TAG_DD_NEXT_ID = 2079, + ISNS_VENDOR_SPECIFIC_SERVER_BASE = 257, + ISNS_VENDOR_SPECIFIC_ENTITY_BASE = 385, + ISNS_VENDOR_SPECIFIC_PORTAL_BASE = 513, + ISNS_VENDOR_SPECIFIC_NODE_BASE = 641, + ISNS_VENDOR_SPECIFIC_DD_BASE = 1024, + ISNS_VENDOR_SPECIFIC_DDSET_BASE = 1281, + ISNS_VENDOR_SPECIFIC_OTHER_BASE = 1537, + }, - -- - -- Creates a new Attribute instance - -- - -- @param tag number containing the tag number - -- @param val string containing the tag value - -- @param len number containing the tag length - -- @return o new Attribute instance - new = function(self, tag, val, len) - local o = { tag = tag, len = ( len or (val and #val or 0) ), val = val or "" } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a new Attribute instance + -- + -- @param tag number containing the tag number + -- @param val string containing the tag value + -- @param len number containing the tag length + -- @return o new Attribute instance + new = function(self, tag, val, len) + local o = { tag = tag, len = ( len or (val and #val or 0) ), val = val or "" } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Creates a new Attribute instance - -- - -- @param data string containing an opaque string of raw data - -- @return attr new instance of Attribute - parse = function(data) - local attr = Attribute:new() - local pos + -- + -- Creates a new Attribute instance + -- + -- @param data string containing an opaque string of raw data + -- @return attr new instance of Attribute + parse = function(data) + local attr = Attribute:new() + local pos - pos, attr.tag, attr.len = bin.unpack(">II", data) - pos, attr.val = bin.unpack(">A" .. attr.len, pos) + pos, attr.tag, attr.len = bin.unpack(">II", data) + pos, attr.val = bin.unpack(">A" .. attr.len, pos) - return attr - end, + return attr + end, - -- - -- Converts the instance to an opaque string - -- @return str containing an opaque string - __tostring = function(self) - return bin.pack(">IIA", self.tag, self.len, self.val) - end, + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + return bin.pack(">IIA", self.tag, self.len, self.val) + end, } Attributes = { - -- - -- Creates a new Attributes table - -- @return o new instance of Attributes - new = function(self) - local o = { attribs = {} } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a new Attributes table + -- @return o new instance of Attributes + new = function(self) + local o = { attribs = {} } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Adds a new Attribute to the table - -- @param tag number containing the tag number - -- @param val string containing the tag value - -- @param len number containing the tag length - add = function(self, tag, val, len) - table.insert(self, Attribute:new(tag, val, len)) - end, + -- + -- Adds a new Attribute to the table + -- @param tag number containing the tag number + -- @param val string containing the tag value + -- @param len number containing the tag length + add = function(self, tag, val, len) + table.insert(self, Attribute:new(tag, val, len)) + end, - -- - -- Converts the instance to an opaque string - -- @return str containing an opaque string - __tostring = function(self) - local str = "" - for _, attr in ipairs(self) do - str = str .. tostring(attr) - end - return str - end, + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + local str = "" + for _, attr in ipairs(self) do + str = str .. tostring(attr) + end + return str + end, } Request = { - FuncId = { - DevAttrReg = 0x0001, - DevAttrQry = 0x0002, - DevGetNext = 0x0003, - DevDereg = 0x0004, - SCNReg = 0x0005, - SCNDereg = 0x0006, - SCNEvent = 0x0007, - SCN = 0x0008, - DDReg = 0x0009, - DDDereg = 0x000A, - DDSReg = 0x000B, - DDSDereg = 0x000C, - ESI = 0x000D, - Heartbeat = 0x000E, - }, + FuncId = { + DevAttrReg = 0x0001, + DevAttrQry = 0x0002, + DevGetNext = 0x0003, + DevDereg = 0x0004, + SCNReg = 0x0005, + SCNDereg = 0x0006, + SCNEvent = 0x0007, + SCN = 0x0008, + DDReg = 0x0009, + DDDereg = 0x000A, + DDSReg = 0x000B, + DDSDereg = 0x000C, + ESI = 0x000D, + Heartbeat = 0x000E, + }, - -- - -- Creates a new Request message - -- @param func_id number containing the function ID of the message - -- @param flags number containing the message flags - -- @param data string containing the opaque raw data - -- @param auth string containing the opaqur raw auth data - -- @param trans_id number containing the transaction id - -- @param seq_id number containing the sequence id - -- @return o new instance of Request - new = function(self, func_id, flags, data, auth, trans_id, seq_id) - local o = { - header = Header:new(func_id, ( data and #data ) or 0, flags, ( trans_id or -1 ), ( seq_id or -1 )), - data = data or "" - } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a new Request message + -- @param func_id number containing the function ID of the message + -- @param flags number containing the message flags + -- @param data string containing the opaque raw data + -- @param auth string containing the opaqur raw auth data + -- @param trans_id number containing the transaction id + -- @param seq_id number containing the sequence id + -- @return o new instance of Request + new = function(self, func_id, flags, data, auth, trans_id, seq_id) + local o = { + header = Header:new(func_id, ( data and #data ) or 0, flags, ( trans_id or -1 ), ( seq_id or -1 )), + data = data or "" + } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Converts the instance to an opaque string - -- @return str containing an opaque string - __tostring = function(self) - return tostring(self.header) .. tostring(self.data) .. - ( self.auth and self.auth or "" ) - end, + -- + -- Converts the instance to an opaque string + -- @return str containing an opaque string + __tostring = function(self) + return tostring(self.header) .. tostring(self.data) .. + ( self.auth and self.auth or "" ) + end, } Response = { - Error = { - [0] = "Successful", - [1] = "Unknown Error", - [2] = "Message Format Error", - [3] = "Invalid Registration", - [4] = "RESERVED", - [5] = "Invalid Query", - [6] = "Source Unknown", - [7] = "Source Absent", - [8] = "Source Unauthorized", - [9] = "No Such Entry", - [10] = "Version Not Supported", - [11] = "Internal Error", - [12] = "Busy", - [13] = "Option Not Understood", - [14] = "Invalid Update", - [15] = "Message (FUNCTION_ID) Not Supported", - [16] = "SCN Event Rejected", - [17] = "SCN Registration Rejected", - [18] = "Attribute Not Implemented", - [19] = "FC_DOMAIN_ID Not Available", - [20] = "FC_DOMAIN_ID Not Allocated", - [21] = "ESI Not Available", - [22] = "Invalid Deregistration", - [23] = "Registration Feature Not Supported", - }, + Error = { + [0] = "Successful", + [1] = "Unknown Error", + [2] = "Message Format Error", + [3] = "Invalid Registration", + [4] = "RESERVED", + [5] = "Invalid Query", + [6] = "Source Unknown", + [7] = "Source Absent", + [8] = "Source Unauthorized", + [9] = "No Such Entry", + [10] = "Version Not Supported", + [11] = "Internal Error", + [12] = "Busy", + [13] = "Option Not Understood", + [14] = "Invalid Update", + [15] = "Message (FUNCTION_ID) Not Supported", + [16] = "SCN Event Rejected", + [17] = "SCN Registration Rejected", + [18] = "Attribute Not Implemented", + [19] = "FC_DOMAIN_ID Not Available", + [20] = "FC_DOMAIN_ID Not Allocated", + [21] = "ESI Not Available", + [22] = "Invalid Deregistration", + [23] = "Registration Feature Not Supported", + }, - -- - -- Creates a new Response instance - -- @return o new instance of Response - new = function(self) - local o = { attrs = Attributes:new() } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a new Response instance + -- @return o new instance of Response + new = function(self) + local o = { attrs = Attributes:new() } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Creates a new Response instance - -- - -- @param data string containing an opaque string of raw data - -- @return attr new instance of Response - parse = function(data) - local hdr = Header.parse(data) - local pos = #(tostring(hdr)) + 1 - local resp = Response:new() + -- + -- Creates a new Response instance + -- + -- @param data string containing an opaque string of raw data + -- @return attr new instance of Response + parse = function(data) + local hdr = Header.parse(data) + local pos = #(tostring(hdr)) + 1 + local resp = Response:new() - pos, resp.error = bin.unpack(">I", data, pos) - if ( resp.error ~= 0 ) then - return resp - end + pos, resp.error = bin.unpack(">I", data, pos) + if ( resp.error ~= 0 ) then + return resp + end - while( pos < #data ) do - local tag, len, val - pos, tag, len = bin.unpack(">II", data, pos) - pos, val = bin.unpack("A" .. len, data, pos) - resp.attrs:add( tag, val, len ) - end - return resp - end, + while( pos < #data ) do + local tag, len, val + pos, tag, len = bin.unpack(">II", data, pos) + pos, val = bin.unpack("A" .. len, data, pos) + resp.attrs:add( tag, val, len ) + end + return resp + end, } Session = { - -- - -- Creates a new Session instance - -- @param host table - -- @param port table - -- @return o instance of Session - new = function(self, host, port) - local o = { - host = host, - port = port, - seq_id = 0, - trans_id = 0, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a new Session instance + -- @param host table + -- @param port table + -- @return o instance of Session + new = function(self, host, port) + local o = { + host = host, + port = port, + seq_id = 0, + trans_id = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Connects to the server - -- @return status true on success, false on failure - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(5000) - return self.socket:connect(self.host, self.port) - end, + -- + -- Connects to the server + -- @return status true on success, false on failure + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(5000) + return self.socket:connect(self.host, self.port) + end, - -- - -- Sends data to the server - -- @return status true on success, false on failure - -- @return err string containing the error message on failure - send = function(self, req) - if ( not(req.header) or not(req.header.seq_id) or not(req.header.trans_id) ) then - return false, "Failed to send invalid request" - end + -- + -- Sends data to the server + -- @return status true on success, false on failure + -- @return err string containing the error message on failure + send = function(self, req) + if ( not(req.header) or not(req.header.seq_id) or not(req.header.trans_id) ) then + return false, "Failed to send invalid request" + end - -- update the sequence and transaction ID's - req.header.seq_id = self.seq_id - req.header.trans_id = self.trans_id + -- update the sequence and transaction ID's + req.header.seq_id = self.seq_id + req.header.trans_id = self.trans_id - local status, err = self.socket:send(tostring(req)) - self.trans_id = self.trans_id + 1 + local status, err = self.socket:send(tostring(req)) + self.trans_id = self.trans_id + 1 - return status, err - end, + return status, err + end, - -- - -- Receives data from the server - -- @return status true on success, false on failure - -- @return response instance of response - receive = function(self) - -- receive the 24 byte header - local status, buf_hdr = self.socket:receive_buf(match.numbytes(12), true) - if ( not(status) ) then - return status, buf_hdr - end + -- + -- Receives data from the server + -- @return status true on success, false on failure + -- @return response instance of response + receive = function(self) + -- receive the 24 byte header + local status, buf_hdr = self.socket:receive_buf(match.numbytes(12), true) + if ( not(status) ) then + return status, buf_hdr + end - local hdr = Header.parse(buf_hdr) + local hdr = Header.parse(buf_hdr) - -- receive the data - local buf_data = nil - status, buf_data = self.socket:receive_buf(match.numbytes(hdr.pdu_len), true) - if ( not(status) ) then - return status, buf_data - end + -- receive the data + local buf_data = nil + status, buf_data = self.socket:receive_buf(match.numbytes(hdr.pdu_len), true) + if ( not(status) ) then + return status, buf_data + end - return true, Response.parse(buf_hdr .. buf_data) - end, + return true, Response.parse(buf_hdr .. buf_data) + end, - close = function(self) - return self.close() - end + close = function(self) + return self.close() + end } Helper = { - -- - -- Creates a new Helper instance - -- @param host param - -- @param port param - -- @return o new instance of Helper - new = function(self, host, port) - local o = { session = Session:new(host, port) } - setmetatable(o, self) - self.__index = self - return o - end, + -- + -- Creates a new Helper instance + -- @param host param + -- @param port param + -- @return o new instance of Helper + new = function(self, host, port) + local o = { session = Session:new(host, port) } + setmetatable(o, self) + self.__index = self + return o + end, - -- - -- Connects to the server - -- @return status true on success, false on failure - connect = function(self) - return self.session:connect() - end, + -- + -- Connects to the server + -- @return status true on success, false on failure + connect = function(self) + return self.session:connect() + end, - -- - -- Lists portals - -- @return status true on success, false on failure - -- @return resulst list of iSCSI nodes, err string on failure - listPortals = function(self) - local attribs, name = Attributes:new(), "iqn.control.node\0por" + -- + -- Lists portals + -- @return status true on success, false on failure + -- @return resulst list of iSCSI nodes, err string on failure + listPortals = function(self) + local attribs, name = Attributes:new(), "iqn.control.node\0por" - attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name) - attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS) - attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER) - attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS) - attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT) - attribs:add(Attribute.Tag.ISNS_TAG_ENTITY_IDENTIFIER) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name) + attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS) + attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER) + attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS) + attribs:add(Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT) + attribs:add(Attribute.Tag.ISNS_TAG_ENTITY_IDENTIFIER) - local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU + local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU - local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs)) - if ( not(self.session:send(req)) ) then - return false, "Failed to send message to server" - end + local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs)) + if ( not(self.session:send(req)) ) then + return false, "Failed to send message to server" + end - local status, resp = self.session:receive() - if ( not(status) ) then - return false, "Failed to receive message from server" - end + local status, resp = self.session:receive() + if ( not(status) ) then + return false, "Failed to receive message from server" + end - local results = {} - local addr, proto, port - for _, attr in ipairs(resp.attrs) do - if ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS ) then - addr = attr.val - local pos, is_ipv4 = bin.unpack("A12", addr) - if ( is_ipv4 == "\0\0\0\0\0\0\0\0\0\0\xFF\xFF" ) then - local pos, bin_ip = bin.unpack("B4", addr, 13) - addr = ipops.bin_to_ip(bin_ip) - else - local pos, bin_ip = bin.unpack("B16", addr) - addr = ipops.bin_to_ip(bin_ip) - end - elseif ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT ) then - local pos, s1 - pos, s1, port = bin.unpack(">SS", attr.val) + local results = {} + local addr, proto, port + for _, attr in ipairs(resp.attrs) do + if ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_IP_ADDRESS ) then + addr = attr.val + local pos, is_ipv4 = bin.unpack("A12", addr) + if ( is_ipv4 == "\0\0\0\0\0\0\0\0\0\0\xFF\xFF" ) then + local pos, bin_ip = bin.unpack("B4", addr, 13) + addr = ipops.bin_to_ip(bin_ip) + else + local pos, bin_ip = bin.unpack("B16", addr) + addr = ipops.bin_to_ip(bin_ip) + end + elseif ( attr.tag == Attribute.Tag.ISNS_TAG_PORTAL_TCP_UDP_PORT ) then + local pos, s1 + pos, s1, port = bin.unpack(">SS", attr.val) - if ( s1 == 1 ) then - proto = "udp" - elseif ( s1 == 0 ) then - proto = "tcp" - else - proto = "UNKNOWN" - end - elseif ( addr and proto and port ) then - table.insert(results, { addr = addr, proto = proto, port = port } ) - addr, proto, port = nil, nil, nil - end - end - return true, results - end, + if ( s1 == 1 ) then + proto = "udp" + elseif ( s1 == 0 ) then + proto = "tcp" + else + proto = "UNKNOWN" + end + elseif ( addr and proto and port ) then + table.insert(results, { addr = addr, proto = proto, port = port } ) + addr, proto, port = nil, nil, nil + end + end + return true, results + end, - -- - -- Lists iSCSI nodes - -- @return status true on success, false on failure - -- @return resulst list of iSCSI nodes, err string on failure - listISCINodes = function(self) - local attribs = Attributes:new() - local name = "iqn.control.node\0por" - attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name) - attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME) - attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER) - attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME) - attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE) + -- + -- Lists iSCSI nodes + -- @return status true on success, false on failure + -- @return resulst list of iSCSI nodes, err string on failure + listISCINodes = function(self) + local attribs = Attributes:new() + local name = "iqn.control.node\0por" + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME, name) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME) + attribs:add(Attribute.Tag.ISNS_TAG_DELIMITER) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NAME) + attribs:add(Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE) - local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU + local flags = 0x8c00 -- Sender is iSNS client, Last PDU, First PDU - local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs)) - if ( not(self.session:send(req)) ) then - return false, "Failed to send message to server" - end + local req = Request:new(Request.FuncId.DevAttrQry, flags, tostring(attribs)) + if ( not(self.session:send(req)) ) then + return false, "Failed to send message to server" + end - local status, resp = self.session:receive() - if ( not(status) ) then - return false, "Failed to receive message from server" - end + local status, resp = self.session:receive() + if ( not(status) ) then + return false, "Failed to receive message from server" + end - local name, ntype - local results = {} - for _, attr in ipairs(resp.attrs) do - if ( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NAME ) then - name = attr.val - elseif( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE ) then - local _, val = bin.unpack(">I", attr.val) - if ( val == iSCSI.NodeType.CONTROL ) then - ntype = "Control" - elseif ( val == iSCSI.NodeType.INITIATOR ) then - ntype = "Initiator" - elseif ( val == iSCSI.NodeType.TARGET ) then - ntype = "Target" - else - ntype = "Unknown" - end - end - if ( name and ntype ) then - table.insert(results, { name = name:match("^([^\0]*)"), type = ntype }) - name, ntype = nil, nil - end - end - return true, results - end, + local name, ntype + local results = {} + for _, attr in ipairs(resp.attrs) do + if ( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NAME ) then + name = attr.val + elseif( attr.tag == Attribute.Tag.ISNS_TAG_ISCSI_NODE_TYPE ) then + local _, val = bin.unpack(">I", attr.val) + if ( val == iSCSI.NodeType.CONTROL ) then + ntype = "Control" + elseif ( val == iSCSI.NodeType.INITIATOR ) then + ntype = "Initiator" + elseif ( val == iSCSI.NodeType.TARGET ) then + ntype = "Target" + else + ntype = "Unknown" + end + end + if ( name and ntype ) then + table.insert(results, { name = name:match("^([^\0]*)"), type = ntype }) + name, ntype = nil, nil + end + end + return true, results + end, - close = function(self) - return self.session:close() - end, + close = function(self) + return self.session:close() + end, } diff --git a/nselib/json.lua b/nselib/json.lua index 9cc0d752e..88607b163 100644 --- a/nselib/json.lua +++ b/nselib/json.lua @@ -32,21 +32,21 @@ _ENV = stdnse.module("json", stdnse.seeall) --Some local shortcuts local function dbg(str,...) - stdnse.print_debug("Json:"..str, ...) + stdnse.print_debug("Json:"..str, ...) end local function d4(str,...) - if nmap.debugging() > 3 then dbg(str, ...) end + if nmap.debugging() > 3 then dbg(str, ...) end end local function d3(str,...) - if nmap.debugging() > 2 then dbg(str, ...) end + if nmap.debugging() > 2 then dbg(str, ...) end end --local dbg =stdnse.print_debug local function dbg_err(str,...) - stdnse.print_debug("json-ERR:"..str, ...) + stdnse.print_debug("json-ERR:"..str, ...) end - -- Javascript null representation, see explanation above +-- Javascript null representation, see explanation above NULL = {} -- See section 2.5 for escapes. @@ -56,45 +56,45 @@ NULL = {} local ESCAPE_TABLE = {} local REVERSE_ESCAPE_TABLE = {} do - local escapes = { - [string.char(0x22)] = "\"", - [string.char(0x5C)] = "\\", - [string.char(0x2F)] = "/", - [string.char(0x08)] = "b", - [string.char(0x0C)] = "f", - [string.char(0x0A)] = "n", - [string.char(0x0D)] = "r", - [string.char(0x09)] = "t", - } - for k, v in pairs(escapes) do - ESCAPE_TABLE[k] = "\\" .. v - REVERSE_ESCAPE_TABLE[v] = k - end + local escapes = { + [string.char(0x22)] = "\"", + [string.char(0x5C)] = "\\", + [string.char(0x2F)] = "/", + [string.char(0x08)] = "b", + [string.char(0x0C)] = "f", + [string.char(0x0A)] = "n", + [string.char(0x0D)] = "r", + [string.char(0x09)] = "t", + } + for k, v in pairs(escapes) do + ESCAPE_TABLE[k] = "\\" .. v + REVERSE_ESCAPE_TABLE[v] = k + end end -- Escapes a string --@param str the string --@return a string where the special chars have been escaped local function escape(str) - return "\"" .. string.gsub(str, ".", ESCAPE_TABLE) .. "\"" + return "\"" .. string.gsub(str, ".", ESCAPE_TABLE) .. "\"" end --- Makes a table be treated as a JSON Array when generating JSON -- A table treated as an Array has all non-number indices ignored. -- () param t a table to be treated as an array function make_array(t) - local mt = getmetatable(t) or {} - mt["json"] = "array" - setmetatable(t, mt) + local mt = getmetatable(t) or {} + mt["json"] = "array" + setmetatable(t, mt) end --- Makes a table be treated as a JSON Object when generating JSON -- A table treated as an Object has all non-number indices ignored. -- () param t a table to be treated as an object function make_object(t) - local mt = getmetatable(t) or {} - mt["json"] = "object" - setmetatable(t, mt) + local mt = getmetatable(t) or {} + mt["json"] = "object" + setmetatable(t, mt) end --- Checks what JSON type a variable will be treated as when generating JSON @@ -102,20 +102,20 @@ end -- () return a string containing the JSON type. Valid values are "array", -- "object", "number", "string", "boolean", and "null" function typeof(var) - local t = type(var) - if var == NULL then - return "null" - elseif t == "table" then - local mtval = rawget(getmetatable(var) or {}, "json") - if mtval == "array" or (mtval ~= "object" and #var > 0) then - return "array" - else - return "object" - end - else - return t - end - error("Unknown data type in typeof") + local t = type(var) + if var == NULL then + return "null" + elseif t == "table" then + local mtval = rawget(getmetatable(var) or {}, "json") + if mtval == "array" or (mtval ~= "object" and #var > 0) then + return "array" + else + return "object" + end + else + return t + end + error("Unknown data type in typeof") end --- Creates json data from an object @@ -123,22 +123,22 @@ end --@return a string containing valid json function generate(obj) - -- NULL-check must be performed before - -- checking type == table, since the NULL-object - -- is a table - if obj == NULL then - return "null" - elseif obj == false then - return "false" - elseif obj == true then - return "true" - elseif type(obj) == "number" then - return string.format("%g", obj) - elseif type(obj) == "string" then - return escape(obj) - elseif type(obj) == "table" then - local k, v, elems, jtype - elems = {} + -- NULL-check must be performed before + -- checking type == table, since the NULL-object + -- is a table + if obj == NULL then + return "null" + elseif obj == false then + return "false" + elseif obj == true then + return "true" + elseif type(obj) == "number" then + return string.format("%g", obj) + elseif type(obj) == "string" then + return escape(obj) + elseif type(obj) == "table" then + local k, v, elems, jtype + elems = {} jtype = typeof(obj) if jtype == "array" then for _, v in ipairs(obj) do @@ -151,54 +151,54 @@ function generate(obj) end return "{" .. table.concat(elems, ", ") .. "}" end - end - error("Unknown data type in generate") + end + error("Unknown data type in generate") end -- This is the parser, implemented in OO-form to deal with state better Json = {} -- Constructor function Json:new(input) - local o = {} - setmetatable(o, self) - self.__index = self - o.input = input - o.pos = 1 -- Pos is where the NEXT letter will be read - return o + local o = {} + setmetatable(o, self) + self.__index = self + o.input = input + o.pos = 1 -- Pos is where the NEXT letter will be read + return o end -- Gets next character and ups the position --@return next character function Json:next() - self.pos = self.pos+1 - return self.input:sub(self.pos-1, self.pos-1) + self.pos = self.pos+1 + return self.input:sub(self.pos-1, self.pos-1) end -- Updates the position to next non whitespace position function Json:eatWhiteSpace() - --Find next non-white char - local a,b = self.input:find("%S",self.pos) - if not a then - self:syntaxerror("Empty data") - return - end - self.pos = a + --Find next non-white char + local a,b = self.input:find("%S",self.pos) + if not a then + self:syntaxerror("Empty data") + return + end + self.pos = a end -- Jumps to a specified position --@param position where to go function Json:jumpTo(position) - self.pos = position + self.pos = position end -- Returns next character, but without upping position --@return next character function Json:peek() - return self.input:sub(self.pos, self.pos) + return self.input:sub(self.pos, self.pos) end --@return true if more input is in store function Json:hasMore() - return self.input:len() >= self.pos + return self.input:len() >= self.pos end -- Checks that the following input is equal to a string @@ -206,270 +206,270 @@ end -- If false, triggers a syntax error --@param str the string to test function Json:assertStr(str) - local content = self.input:sub(self.pos,self.pos+str:len()-1) - if(content == str) then-- All ok - -- Jump forward - self:jumpTo(self.pos+str:len()) - return - end - self:syntaxerror(("Expected '%s' but got '%s'"):format( str, content)) + local content = self.input:sub(self.pos,self.pos+str:len()-1) + if(content == str) then-- All ok + -- Jump forward + self:jumpTo(self.pos+str:len()) + return + end + self:syntaxerror(("Expected '%s' but got '%s'"):format( str, content)) end -- Trigger a syntax error function Json:syntaxerror(reason) - self.error = ("Syntax error near pos %d: %s input: %s"):format( self.pos, reason, self.input) - dbg(self.error) + self.error = ("Syntax error near pos %d: %s input: %s"):format( self.pos, reason, self.input) + dbg(self.error) end -- Check if any errors has occurred function Json:errors() - return self.error ~= nil + return self.error ~= nil end -- Parses a top-level JSON structure (object or array). --@return the parsed object or puts error messages in self.error function Json:parseStart() - -- The top level of JSON only allows an object or an array. Only inside - -- of the outermost container can other types appear. - self:eatWhiteSpace() - local c = self:peek() - if c == '{' then - return self:parseObject() - elseif c == '[' then - return self:parseArray() - else - self:syntaxerror(("JSON must start with object or array (started with %s)"):format(c)) - return - end + -- The top level of JSON only allows an object or an array. Only inside + -- of the outermost container can other types appear. + self:eatWhiteSpace() + local c = self:peek() + if c == '{' then + return self:parseObject() + elseif c == '[' then + return self:parseArray() + else + self:syntaxerror(("JSON must start with object or array (started with %s)"):format(c)) + return + end end -- Parses a value --@return the parsed value function Json:parseValue() - self:eatWhiteSpace() - local c = self:peek() + self:eatWhiteSpace() + local c = self:peek() - local value - if c == '{' then - value = self:parseObject() - elseif c == '[' then - value = self:parseArray() - elseif c == '"' then - value = self:parseString() - elseif c == 'n' then - self:assertStr("null") - value = NULL - elseif c == 't' then - self:assertStr("true") - value = true - elseif c == 'f' then - self:assertStr("false") - value = false - else -- numeric - -- number = [ minus ] int [ frac ] [ exp ] - local a,b =self.input:find("-?%d+%.?%d*[eE]?[+-]?%d*", self.pos) - if not a or not b then - self:syntaxerror("Error 1 parsing numeric value") - return - end - value = tonumber(self.input:sub(a,b)) - if(value == nil) then - self:syntaxerror("Error 2 parsing numeric value") - return - end - self:jumpTo(b+1) - end - return value + local value + if c == '{' then + value = self:parseObject() + elseif c == '[' then + value = self:parseArray() + elseif c == '"' then + value = self:parseString() + elseif c == 'n' then + self:assertStr("null") + value = NULL + elseif c == 't' then + self:assertStr("true") + value = true + elseif c == 'f' then + self:assertStr("false") + value = false + else -- numeric + -- number = [ minus ] int [ frac ] [ exp ] + local a,b =self.input:find("-?%d+%.?%d*[eE]?[+-]?%d*", self.pos) + if not a or not b then + self:syntaxerror("Error 1 parsing numeric value") + return + end + value = tonumber(self.input:sub(a,b)) + if(value == nil) then + self:syntaxerror("Error 2 parsing numeric value") + return + end + self:jumpTo(b+1) + end + return value end -- Parses a json object {} --@return the object (or triggers a syntax error) function Json:parseObject() - local object = {} - make_object(object) - local _= self:next() -- Eat { + local object = {} + make_object(object) + local _= self:next() -- Eat { - while(self:hasMore() and not self:errors()) do - self:eatWhiteSpace() - local c = self:peek() - if(c == '}') then -- Empty object, probably - self:next() -- Eat it - return object - end + while(self:hasMore() and not self:errors()) do + self:eatWhiteSpace() + local c = self:peek() + if(c == '}') then -- Empty object, probably + self:next() -- Eat it + return object + end - if(c ~= '"') then - self:syntaxerror(("Expected '\"', got '%s'"):format(c)) - return - end + if(c ~= '"') then + self:syntaxerror(("Expected '\"', got '%s'"):format(c)) + return + end - local key = self:parseString() - if self:errors() then - return - end - self:eatWhiteSpace() - c = self:next() - if(c ~= ':') then - self:syntaxerror("Expected ':' got "..c) - return - end - local value = self:parseValue() + local key = self:parseString() + if self:errors() then + return + end + self:eatWhiteSpace() + c = self:next() + if(c ~= ':') then + self:syntaxerror("Expected ':' got "..c) + return + end + local value = self:parseValue() - if self:errors() then - return - end + if self:errors() then + return + end - object[key] = value + object[key] = value - self:eatWhiteSpace() - c = self:next() - -- Valid now is , or } - if(c == '}') then - return object - end - if(c ~= ',') then - self:syntaxerror("Expected ',' or '}', got "..c) - return - end - end + self:eatWhiteSpace() + c = self:next() + -- Valid now is , or } + if(c == '}') then + return object + end + if(c ~= ',') then + self:syntaxerror("Expected ',' or '}', got "..c) + return + end + end end -- Parses a json array [] or triggers a syntax error --@return the array object function Json:parseArray() - local array = {} - make_array(array) - self:next() - while(self:hasMore() and not self:errors()) do - self:eatWhiteSpace() - if(self:peek() == ']') then -- Empty array, probably - self:next() - break - end - local value = self:parseValue() - if self:errors() then - return - end - table.insert(array, value) - self:eatWhiteSpace() - local c = self:next() - -- Valid now is , or ] - if(c == ']') then return array end - if(c ~= ',') then - self:syntaxerror(("Expected ',' but got '%s'"):format(c)) - return - end - end - return array + local array = {} + make_array(array) + self:next() + while(self:hasMore() and not self:errors()) do + self:eatWhiteSpace() + if(self:peek() == ']') then -- Empty array, probably + self:next() + break + end + local value = self:parseValue() + if self:errors() then + return + end + table.insert(array, value) + self:eatWhiteSpace() + local c = self:next() + -- Valid now is , or ] + if(c == ']') then return array end + if(c ~= ',') then + self:syntaxerror(("Expected ',' but got '%s'"):format(c)) + return + end + end + return array end -- Decode a Unicode escape, assuming that self.pos starts just after the -- initial \u. May consume an additional escape in the case of a UTF-16 -- surrogate pair. See RFC 2781 for UTF-16. function Json:parseUnicodeEscape() - local n, cp - local hex, lowhex - local s, e + local n, cp + local hex, lowhex + local s, e - s, e, hex = self.input:find("^(....)", self.pos) - if not hex then - self:syntaxerror(("EOF in Unicode escape \\u%s"):format(self.input:sub(self.pos))) - return - end - n = tonumber(hex, 16) - if not n then - self:syntaxerror(("Bad unicode escape \\u%s"):format(hex)) - return - end - cp = n - self.pos = e + 1 - if n < 0xD800 or n > 0xDFFF then - return cp - end - if n >= 0xDC00 and n <= 0xDFFF then - self:syntaxerror(("Not a Unicode character: U+%04X"):format(cp)) - return - end + s, e, hex = self.input:find("^(....)", self.pos) + if not hex then + self:syntaxerror(("EOF in Unicode escape \\u%s"):format(self.input:sub(self.pos))) + return + end + n = tonumber(hex, 16) + if not n then + self:syntaxerror(("Bad unicode escape \\u%s"):format(hex)) + return + end + cp = n + self.pos = e + 1 + if n < 0xD800 or n > 0xDFFF then + return cp + end + if n >= 0xDC00 and n <= 0xDFFF then + self:syntaxerror(("Not a Unicode character: U+%04X"):format(cp)) + return + end - -- Beginning of a UTF-16 surrogate. - s, e, lowhex = self.input:find("^\\u(....)", self.pos) - if not lowhex then - self:syntaxerror(("Bad unicode escape \\u%s (missing low surrogate)"):format(hex)) - return - end - n = tonumber(lowhex, 16) - if not n or not (n >= 0xDC00 and n <= 0xDFFF) then - self:syntaxerror(("Bad unicode escape \\u%s\\u%s (bad low surrogate)"):format(hex, lowhex)) - return - end - self.pos = e + 1 - cp = 0x10000 + bit.band(cp, 0x3FF) * 0x400 + bit.band(n, 0x3FF) - -- also remove last " - return cp + -- Beginning of a UTF-16 surrogate. + s, e, lowhex = self.input:find("^\\u(....)", self.pos) + if not lowhex then + self:syntaxerror(("Bad unicode escape \\u%s (missing low surrogate)"):format(hex)) + return + end + n = tonumber(lowhex, 16) + if not n or not (n >= 0xDC00 and n <= 0xDFFF) then + self:syntaxerror(("Bad unicode escape \\u%s\\u%s (bad low surrogate)"):format(hex, lowhex)) + return + end + self.pos = e + 1 + cp = 0x10000 + bit.band(cp, 0x3FF) * 0x400 + bit.band(n, 0x3FF) + -- also remove last " + return cp end -- Encode a Unicode code point to UTF-8. See RFC 3629. -- Does not check that cp is a real charaacter; that is, doesn't exclude the -- surrogate range U+D800 - U+DFFF and a handful of others. local function utf8_enc(cp) - local bytes = {} - local n, mask + local bytes = {} + local n, mask - if cp % 1.0 ~= 0.0 or cp < 0 then - -- Only defined for nonnegative integers. - return nil - elseif cp <= 0x7F then - -- Special case of one-byte encoding. - return string.char(cp) - elseif cp <= 0x7FF then - n = 2 - mask = 0xC0 - elseif cp <= 0xFFFF then - n = 3 - mask = 0xE0 - elseif cp <= 0x10FFFF then - n = 4 - mask = 0xF0 - else - return nil - end + if cp % 1.0 ~= 0.0 or cp < 0 then + -- Only defined for nonnegative integers. + return nil + elseif cp <= 0x7F then + -- Special case of one-byte encoding. + return string.char(cp) + elseif cp <= 0x7FF then + n = 2 + mask = 0xC0 + elseif cp <= 0xFFFF then + n = 3 + mask = 0xE0 + elseif cp <= 0x10FFFF then + n = 4 + mask = 0xF0 + else + return nil + end - while n > 1 do - bytes[n] = string.char(0x80 + bit.band(cp, 0x3F)) - cp = bit.rshift(cp, 6) - n = n - 1 - end - bytes[1] = string.char(mask + cp) + while n > 1 do + bytes[n] = string.char(0x80 + bit.band(cp, 0x3F)) + cp = bit.rshift(cp, 6) + n = n - 1 + end + bytes[1] = string.char(mask + cp) - return table.concat(bytes) + return table.concat(bytes) end -- Parses a json string -- @return the string or triggers syntax error function Json:parseString() - local val = '' - local c = self:next() - assert( c == '"') - while(self:hasMore()) do - local c = self:next() + local val = '' + local c = self:next() + assert( c == '"') + while(self:hasMore()) do + local c = self:next() - if(c == '"') then -- end of string - break - elseif(c == '\\') then-- Escaped char - local d = self:next() - if REVERSE_ESCAPE_TABLE[d] ~= nil then - val = val .. REVERSE_ESCAPE_TABLE[d] - elseif d == 'u' then -- Unicode chars - local codepoint = self:parseUnicodeEscape() - if not codepoint then - return - end - val = val .. utf8_enc(codepoint) - else - self:syntaxerror(("Undefined escape character '%s'"):format(d)) - return false - end - else -- Char - val = val .. c - end - end - return val + if(c == '"') then -- end of string + break + elseif(c == '\\') then-- Escaped char + local d = self:next() + if REVERSE_ESCAPE_TABLE[d] ~= nil then + val = val .. REVERSE_ESCAPE_TABLE[d] + elseif d == 'u' then -- Unicode chars + local codepoint = self:parseUnicodeEscape() + if not codepoint then + return + end + val = val .. utf8_enc(codepoint) + else + self:syntaxerror(("Undefined escape character '%s'"):format(d)) + return false + end + else -- Char + val = val .. c + end +end +return val end --- Parses json data into an object form -- This is the method you probably want to use if you @@ -478,12 +478,12 @@ end --@return status true if ok, false if bad --@return an object representing the json, or error message function parse(data) - local parser = Json:new(data) - local result = parser:parseStart() - if(parser.error) then - return false, parser.error - end - return true, result + local parser = Json:new(data) + local result = parser:parseStart() + if(parser.error) then + return false, parser.error + end + return true, result end ---------------------------------------------------------------------------------- @@ -491,52 +491,52 @@ end ---------------------------------------------------------------------------------- local TESTS = { - '{"a":1}', - '{"a":true}', - '{"a": false}', - '{"a": null \r\n, \t "b" \f:"ehlo"}', - '{"a\\"a":"a\\"b\\"c\\"d"}', - '{"foo":"gaz\\"onk", "pi":3.14159,"hello":{ "wo":"rld"}}', - '{"a":1, "b":2}', - '{"foo":"gazonk", "pi":3.14159,"hello":{ "wo":"rl\\td"}}', - '[1,2,3,4,5,null,false,true,"\195\164\195\165\195\182\195\177","bar"]', - '[]',-- This will yield {} in toJson, since in lua there is only one basic datatype - and no difference when empty - '{}', + '{"a":1}', + '{"a":true}', + '{"a": false}', + '{"a": null \r\n, \t "b" \f:"ehlo"}', + '{"a\\"a":"a\\"b\\"c\\"d"}', + '{"foo":"gaz\\"onk", "pi":3.14159,"hello":{ "wo":"rld"}}', + '{"a":1, "b":2}', + '{"foo":"gazonk", "pi":3.14159,"hello":{ "wo":"rl\\td"}}', + '[1,2,3,4,5,null,false,true,"\195\164\195\165\195\182\195\177","bar"]', + '[]',-- This will yield {} in toJson, since in lua there is only one basic datatype - and no difference when empty + '{}', - '', -- error - 'null', -- error - '"abc"', -- error - '{a":1}', -- error - '{"a" bad :1}', -- error - '["a\\\\t"]', -- Should become Lua {"a\\t"} - '[0.0.0]', -- error - '[-1]', - '[-1.123e-2]', - '[5e3]', - '[5e+3]', - '[5E-3]', - '[5.5e3]', - '["a\\\\"]', -- Should become Lua {"a\\"} - '{"a}": 1}', -- Should become Lua {"a}" = 1} - '["key": "value"]', -- error - '["\\u0041"]', -- Should become Lua {"A"} - '["\\uD800"]', -- error - '["\\uD834\\uDD1EX"]', -- Should become Lua {"\240\157\132\158X"} + '', -- error + 'null', -- error + '"abc"', -- error + '{a":1}', -- error + '{"a" bad :1}', -- error + '["a\\\\t"]', -- Should become Lua {"a\\t"} + '[0.0.0]', -- error + '[-1]', + '[-1.123e-2]', + '[5e3]', + '[5e+3]', + '[5E-3]', + '[5.5e3]', + '["a\\\\"]', -- Should become Lua {"a\\"} + '{"a}": 1}', -- Should become Lua {"a}" = 1} + '["key": "value"]', -- error + '["\\u0041"]', -- Should become Lua {"A"} + '["\\uD800"]', -- error + '["\\uD834\\uDD1EX"]', -- Should become Lua {"\240\157\132\158X"} } function test() - print("Tests running") - local i,v,res,status - for i,v in pairs(TESTS) do - print("----------------------------") - print(v) - status,res = parse(v) - if not status then print( res) end - if(status) then - print(generate(res)) - else - print("Error:".. res) - end - end + print("Tests running") + local i,v,res,status + for i,v in pairs(TESTS) do + print("----------------------------") + print(v) + status,res = parse(v) + if not status then print( res) end + if(status) then + print(generate(res)) + else + print("Error:".. res) + end + end end return _ENV; diff --git a/nselib/listop.lua b/nselib/listop.lua index ca07d629d..354cd3b76 100644 --- a/nselib/listop.lua +++ b/nselib/listop.lua @@ -17,21 +17,21 @@ _ENV = stdnse.module("listop", stdnse.seeall) -- Functional programming style 'list' operations - bool is_empty(list) - bool is_list(value) + bool is_empty(list) + bool is_list(value) - value apply(function, list) - list map(function, list) - list filter(function, list) - list flatten(list) - list append(list1, list2) - list cons(value1, value2) - list reverse(list) + value apply(function, list) + list map(function, list) + list filter(function, list) + list flatten(list) + list append(list1, list2) + list cons(value1, value2) + list reverse(list) - value car(list) - value ncar(list, x) - list cdr(list) - list ncdr(list, x) + value car(list) + value ncar(list, x) + list cdr(list) + list ncdr(list, x) where 'list' is an indexed table where 'value' is an lua datatype @@ -59,11 +59,11 @@ end -- @param l A list. -- @return List of function results. function map(f, l) - local results = {} - for _, v in ipairs(l) do - results[#results+1] = f(v); - end - return results; + local results = {} + for _, v in ipairs(l) do + results[#results+1] = f(v); + end + return results; end --- Calls the function with all the elements in the list as the parameters. @@ -90,9 +90,9 @@ end function filter(f, l) local results = {} for i, v in ipairs(l) do - if(f(v)) then - results[#results+1] = v; - end + if(f(v)) then + results[#results+1] = v; + end end return results end @@ -134,7 +134,7 @@ end -- @param v2 value or list. -- @return New list. function cons(v1, v2) - return{ is_list(v1) and {table.unpack(v1)} or v1, is_list(v2) and {table.unpack(v2)} or v2} + return{ is_list(v1) and {table.unpack(v1)} or v1, is_list(v2) and {table.unpack(v2)} or v2} end --- Concatenate two lists and return the result. @@ -142,23 +142,23 @@ end -- @param l2 List. -- @return List. function append(l1, l2) - local results = {table.unpack(l1)} + local results = {table.unpack(l1)} - for _, v in ipairs(l2) do - results[#results+1] = v; - end - return results + for _, v in ipairs(l2) do + results[#results+1] = v; + end + return results end --- Return a list in reverse order. -- @param l List. -- @return Reversed list. function reverse(l) - local results = {} - for i=#l, 1, -1 do - results[#results+1] = l[i]; - end - return results + local results = {} + for i=#l, 1, -1 do + results[#results+1] = l[i]; + end + return results end --- Return a flattened version of a list. The flattened list contains @@ -168,17 +168,17 @@ end -- @param l The list to flatten. -- @return Flattened list. function flatten(l) - local function flat(r, t) - for i, v in ipairs(t) do - if(type(v) == 'table') then - flat(r, v) - else - table.insert(r, v) - end - end - return r + local function flat(r, t) + for i, v in ipairs(t) do + if(type(v) == 'table') then + flat(r, v) + else + table.insert(r, v) + end end - return flat({}, l) + return r + end + return flat({}, l) end return _ENV; diff --git a/nselib/match.lua b/nselib/match.lua index e1f69034c..fc0103296 100644 --- a/nselib/match.lua +++ b/nselib/match.lua @@ -28,12 +28,12 @@ _ENV = stdnse.module("match", stdnse.seeall) -- @see nmap.receive_buf -- @see pcre.exec regex = function(pattern) - local r = pcre.new(pattern, 0,"C") + local r = pcre.new(pattern, 0,"C") - return function(buf) - local s,e = r:exec(buf, 0,0); - return s,e - end + return function(buf) + local s,e = r:exec(buf, 0,0); + return s,e + end end --- Return a function that allows delimiting at a certain number of bytes. @@ -46,13 +46,13 @@ end -- @usage sock:receive_buf(match.numbytes(80)) -- @see nmap.receive_buf numbytes = function(num) - local n = num - return function(buf) - if(#buf >=n) then - return n, n - end - return nil - end + local n = num + return function(buf) + if(#buf >=n) then + return n, n + end + return nil + end end diff --git a/nselib/membase.lua b/nselib/membase.lua index 88cd1bdf7..33170c47b 100644 --- a/nselib/membase.lua +++ b/nselib/membase.lua @@ -18,315 +18,315 @@ _ENV = stdnse.module("membase", stdnse.seeall) -- A minimalistic implementation of the Couchbase Membase TAP protocol TAP = { - -- Operations - Op = { - LIST_SASL_MECHS = 0x20, - AUTHENTICATE = 0x21, - }, + -- Operations + Op = { + LIST_SASL_MECHS = 0x20, + AUTHENTICATE = 0x21, + }, - -- Requests - Request = { + -- Requests + Request = { - -- Header breakdown - -- Field (offset) (value) - -- Magic (0): 0x80 (PROTOCOL_BINARY_REQ) - -- Opcode (1): 0x00 - -- Key length (2-3): 0x0000 (0) - -- Extra length (4): 0x00 - -- Data type (5): 0x00 - -- vbucket (6-7): 0x0000 (0) - -- Total body (8-11): 0x00000000 (0) - -- Opaque (12-15): 0x00000000 (0) - -- CAS (16-23): 0x0000000000000000 (0) - Header = { + -- Header breakdown + -- Field (offset) (value) + -- Magic (0): 0x80 (PROTOCOL_BINARY_REQ) + -- Opcode (1): 0x00 + -- Key length (2-3): 0x0000 (0) + -- Extra length (4): 0x00 + -- Data type (5): 0x00 + -- vbucket (6-7): 0x0000 (0) + -- Total body (8-11): 0x00000000 (0) + -- Opaque (12-15): 0x00000000 (0) + -- CAS (16-23): 0x0000000000000000 (0) + Header = { - -- Creates a new instance of Header - -- @param opcode number containing the operation - -- @return o new instance of Header - new = function(self, opcode) - local o = { - magic = 0x80, - opcode = tonumber(opcode), - keylen = 0x0000, - extlen = 0x00, - data_type = 0x00, - vbucket = 0x0000, - total_body = 0x00000000, - opaque = 0x00000000, - CAS = 0x0000000000000000, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of Header + -- @param opcode number containing the operation + -- @return o new instance of Header + new = function(self, opcode) + local o = { + magic = 0x80, + opcode = tonumber(opcode), + keylen = 0x0000, + extlen = 0x00, + data_type = 0x00, + vbucket = 0x0000, + total_body = 0x00000000, + opaque = 0x00000000, + CAS = 0x0000000000000000, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the header to string - -- @return string containing the Header as string - __tostring = function(self) - return bin.pack(">CCSCCSIIL", self.magic, self.opcode, self.keylen, - self.extlen, self.data_type, self.vbucket, self.total_body, - self.opaque, self.CAS) - end, - }, + -- Converts the header to string + -- @return string containing the Header as string + __tostring = function(self) + return bin.pack(">CCSCCSIIL", self.magic, self.opcode, self.keylen, + self.extlen, self.data_type, self.vbucket, self.total_body, + self.opaque, self.CAS) + end, + }, - -- List SASL authentication mechanism - SASLList = { + -- List SASL authentication mechanism + SASLList = { - -- Creates a new instance of the request - -- @return o instance of request - new = function(self) - local o = { - -- 0x20 SASL List Mechs - header = TAP.Request.Header:new(TAP.Op.LIST_SASL_MECHS) - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the request + -- @return o instance of request + new = function(self) + local o = { + -- 0x20 SASL List Mechs + header = TAP.Request.Header:new(TAP.Op.LIST_SASL_MECHS) + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the request to string - -- @return string containing the request as string - __tostring = function(self) - return tostring(self.header) - end, - }, + -- Converts the request to string + -- @return string containing the request as string + __tostring = function(self) + return tostring(self.header) + end, + }, - -- Authenticates using SASL - Authenticate = { + -- Authenticates using SASL + Authenticate = { - -- Creates a new instance of the request - -- @param username string containing the username - -- @param password string containing the password - -- @param mech string containing the SASL mechanism, currently suppored: - -- PLAIN - plain-text authentication - -- @return o instance of request - new = function(self, username, password, mech) - local o = { - -- 0x20 SASL List Mechs - header = TAP.Request.Header:new(TAP.Op.AUTHENTICATE), - username = username, - password = password, - mech = mech, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the request + -- @param username string containing the username + -- @param password string containing the password + -- @param mech string containing the SASL mechanism, currently suppored: + -- PLAIN - plain-text authentication + -- @return o instance of request + new = function(self, username, password, mech) + local o = { + -- 0x20 SASL List Mechs + header = TAP.Request.Header:new(TAP.Op.AUTHENTICATE), + username = username, + password = password, + mech = mech, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the request to string - -- @return string containing the request as string - __tostring = function(self) - if ( self.mech == "PLAIN" ) then - local mech_params = { self.username, self.password } - local auth_data = sasl.Helper:new(self.mech):encode(table.unpack(mech_params)) + -- Converts the request to string + -- @return string containing the request as string + __tostring = function(self) + if ( self.mech == "PLAIN" ) then + local mech_params = { self.username, self.password } + local auth_data = sasl.Helper:new(self.mech):encode(table.unpack(mech_params)) - self.header.keylen = #self.mech - self.header.total_body = #auth_data + #self.mech - return tostring(self.header) .. self.mech .. auth_data - end - end, + self.header.keylen = #self.mech + self.header.total_body = #auth_data + #self.mech + return tostring(self.header) .. self.mech .. auth_data + end + end, - } + } - }, + }, - -- Responses - Response = { + -- Responses + Response = { - -- The response header - -- Header breakdown - -- Field (offset) (value) - -- Magic (0): 0x81 (PROTOCOL_BINARY_RES) - -- Opcode (1): 0x00 - -- Key length (2-3): 0x0000 (0) - -- Extra length (4): 0x00 - -- Data type (5): 0x00 - -- Status (6-7): 0x0000 (SUCCESS) - -- Total body (8-11): 0x00000005 (5) - -- Opaque (12-15): 0x00000000 (0) - -- CAS (16-23): 0x0000000000000000 (0) - Header = { + -- The response header + -- Header breakdown + -- Field (offset) (value) + -- Magic (0): 0x81 (PROTOCOL_BINARY_RES) + -- Opcode (1): 0x00 + -- Key length (2-3): 0x0000 (0) + -- Extra length (4): 0x00 + -- Data type (5): 0x00 + -- Status (6-7): 0x0000 (SUCCESS) + -- Total body (8-11): 0x00000005 (5) + -- Opaque (12-15): 0x00000000 (0) + -- CAS (16-23): 0x0000000000000000 (0) + Header = { - -- Creates a new instance of Header - -- @param data string containing the raw data - -- @return o new instance of Header - new = function(self, data) - local o = { - data = data - } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- Creates a new instance of Header + -- @param data string containing the raw data + -- @return o new instance of Header + new = function(self, data) + local o = { + data = data + } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- Parse the raw header and populates the class members - -- @return status true on success, false on failure - parse = function(self) - if ( 24 > #self.data ) then - stdnse.print_debug("membase: Header packet too short (%d bytes)", #self.data) - return false, "Packet to short" - end - local pos - pos, self.magic, self.opcode, self.keylen, self.extlen, - self.data_type, self.status, self.total_body, self.opaque, - self.CAS = bin.unpack(">CCSCCSIIL", self.data) - return true - end + -- Parse the raw header and populates the class members + -- @return status true on success, false on failure + parse = function(self) + if ( 24 > #self.data ) then + stdnse.print_debug("membase: Header packet too short (%d bytes)", #self.data) + return false, "Packet to short" + end + local pos + pos, self.magic, self.opcode, self.keylen, self.extlen, + self.data_type, self.status, self.total_body, self.opaque, + self.CAS = bin.unpack(">CCSCCSIIL", self.data) + return true + end - }, + }, - -- Decoders - Decoder = { + -- Decoders + Decoder = { - -- TAP.Op.LIST_SASL_MECHS - [0x20] = { - -- Creates a new instance of the decoder - -- @param data string containing the raw response - -- @return o instance if successfully parsed, nil on failure - -- the member variable mechs contains the - -- supported authentication mechanisms. - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- TAP.Op.LIST_SASL_MECHS + [0x20] = { + -- Creates a new instance of the decoder + -- @param data string containing the raw response + -- @return o instance if successfully parsed, nil on failure + -- the member variable mechs contains the + -- supported authentication mechanisms. + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- Parses the raw response - -- @return true on success - parse = function(self) - self.mechs = self.data - return true - end - }, + -- Parses the raw response + -- @return true on success + parse = function(self) + self.mechs = self.data + return true + end + }, - -- Login response - [0x21] = { - -- Creates a new instance of the decoder - -- @param data string containing the raw response - -- @return o instance if successfully parsed, nil on failure - -- the member variable status contains the - -- servers authentication response. - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- Login response + [0x21] = { + -- Creates a new instance of the decoder + -- @param data string containing the raw response + -- @return o instance if successfully parsed, nil on failure + -- the member variable status contains the + -- servers authentication response. + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- Parses the raw response - -- @return true on success - parse = function(self) - self.status = self.data - return true - end - } + -- Parses the raw response + -- @return true on success + parse = function(self) + self.status = self.data + return true + end + } - } + } - }, + }, } -- The Helper class is the main script interface Helper = { - -- Creates a new instance of the helper - -- @param host table as received by the action method - -- @param port table as received by the action method - -- @param options table including options to the helper, currently: - -- timeout - socket timeout in milliseconds - new = function(self, host, port, options) - local o = { - host = host, - port = port, - mech = stdnse.get_script_args("membase.authmech"), - options = options or {} - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the helper + -- @param host table as received by the action method + -- @param port table as received by the action method + -- @param options table including options to the helper, currently: + -- timeout - socket timeout in milliseconds + new = function(self, host, port, options) + local o = { + host = host, + port = port, + mech = stdnse.get_script_args("membase.authmech"), + options = options or {} + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects the socket to the server - -- @return true on success, false on failure - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(self.options.timeout or 10000) - return self.socket:connect(self.host, self.port) - end, + -- Connects the socket to the server + -- @return true on success, false on failure + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(self.options.timeout or 10000) + return self.socket:connect(self.host, self.port) + end, - -- Closes the socket - close = function(self) - return self.socket:close() - end, + -- Closes the socket + close = function(self) + return self.socket:close() + end, - -- Sends a request to the server, receives and parses the response - -- @param req a Request instance - -- @return status true on success, false on failure - -- @return response instance of Response - exch = function(self, req) - local status, err = self.socket:send(tostring(req)) - if ( not(status) ) then - return false, "Failed to send data" - end + -- Sends a request to the server, receives and parses the response + -- @param req a Request instance + -- @return status true on success, false on failure + -- @return response instance of Response + exch = function(self, req) + local status, err = self.socket:send(tostring(req)) + if ( not(status) ) then + return false, "Failed to send data" + end - local data - status, data = self.socket:receive_buf(match.numbytes(24), true) - if ( not(status) ) then - return false, "Failed to receive data" - end + local data + status, data = self.socket:receive_buf(match.numbytes(24), true) + if ( not(status) ) then + return false, "Failed to receive data" + end - local header = TAP.Response.Header:new(data) + local header = TAP.Response.Header:new(data) - if ( header.opcode ~= req.header.opcode ) then - stdnse.print_debug("WARNING: Received invalid op code, request contained (%d), response contained (%d)", req.header.opcode, header.opcode) - end + if ( header.opcode ~= req.header.opcode ) then + stdnse.print_debug("WARNING: Received invalid op code, request contained (%d), response contained (%d)", req.header.opcode, header.opcode) + end - if ( not(TAP.Response.Decoder[tonumber(header.opcode)]) ) then - return false, ("No response handler for opcode: %d"):format(header.opcode) - end + if ( not(TAP.Response.Decoder[tonumber(header.opcode)]) ) then + return false, ("No response handler for opcode: %d"):format(header.opcode) + end - local status, data = self.socket:receive_buf(match.numbytes(header.total_body), true) - if ( not(status) ) then - return false, "Failed to receive data" - end + local status, data = self.socket:receive_buf(match.numbytes(header.total_body), true) + if ( not(status) ) then + return false, "Failed to receive data" + end - local response = TAP.Response.Decoder[tonumber(header.opcode)]:new(data) - if ( not(response) ) then - return false, "Failed to parse response from server" - end - return true, response - end, + local response = TAP.Response.Decoder[tonumber(header.opcode)]:new(data) + if ( not(response) ) then + return false, "Failed to parse response from server" + end + return true, response + end, - -- Gets list of supported SASL authentication mechanisms - getSASLMechList = function(self) - return self:exch(TAP.Request.SASLList:new()) - end, + -- Gets list of supported SASL authentication mechanisms + getSASLMechList = function(self) + return self:exch(TAP.Request.SASLList:new()) + end, - -- Logins to the server - -- @param username string containing the username - -- @param password string containing the password - -- @param mech string containing the SASL mechanism to use - -- @return status true on success, false on failure - -- @return respons string containing "Auth failure" on failure - login = function(self, username, password, mech) - mech = mech or self.mech or "PLAIN" - local status, response = self:exch(TAP.Request.Authenticate:new(username, password, mech)) - if ( not(status) ) then - return false, "Auth failure" - end - if ( response.status == "Auth failure" ) then - return false, response.status - end - return true, response.status - end, + -- Logins to the server + -- @param username string containing the username + -- @param password string containing the password + -- @param mech string containing the SASL mechanism to use + -- @return status true on success, false on failure + -- @return respons string containing "Auth failure" on failure + login = function(self, username, password, mech) + mech = mech or self.mech or "PLAIN" + local status, response = self:exch(TAP.Request.Authenticate:new(username, password, mech)) + if ( not(status) ) then + return false, "Auth failure" + end + if ( response.status == "Auth failure" ) then + return false, response.status + end + return true, response.status + end, } diff --git a/nselib/mobileme.lua b/nselib/mobileme.lua index 6f36e95e7..944e29f14 100644 --- a/nselib/mobileme.lua +++ b/nselib/mobileme.lua @@ -14,214 +14,214 @@ _ENV = stdnse.module("mobileme", stdnse.seeall) MobileMe = { - -- headers used in all requests - headers = { - ["Content-Type"] = "application/json; charset=utf-8", - ["X-Apple-Find-Api-Ver"] = "2.0", - ["X-Apple-Authscheme"] = "UserIdGuest", - ["X-Apple-Realm-Support"] = "1.0", - ["User-Agent"] = "Find iPhone/1.3 MeKit (iPad: iPhone OS/4.2.1)", - ["X-Client-Name"] = "iPad", - ["X-Client-UUID"] = "0cf3dc501ff812adb0b202baed4f37274b210853", - ["Accept-Language"] = "en-us", - ["Connection"] = "keep-alive" - }, + -- headers used in all requests + headers = { + ["Content-Type"] = "application/json; charset=utf-8", + ["X-Apple-Find-Api-Ver"] = "2.0", + ["X-Apple-Authscheme"] = "UserIdGuest", + ["X-Apple-Realm-Support"] = "1.0", + ["User-Agent"] = "Find iPhone/1.3 MeKit (iPad: iPhone OS/4.2.1)", + ["X-Client-Name"] = "iPad", + ["X-Client-UUID"] = "0cf3dc501ff812adb0b202baed4f37274b210853", + ["Accept-Language"] = "en-us", + ["Connection"] = "keep-alive" + }, - -- Creates a MobileMe instance - -- @param username string containing the Apple ID username - -- @param password string containing the Apple ID password - -- @return o new instance of MobileMe - new = function(self, username, password) - local o = { - host = "fmipmobile.icloud.com", - port = 443, - username = username, - password = password - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a MobileMe instance + -- @param username string containing the Apple ID username + -- @param password string containing the Apple ID password + -- @return o new instance of MobileMe + new = function(self, username, password) + local o = { + host = "fmipmobile.icloud.com", + port = 443, + username = username, + password = password + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Sends a message to an iOS device - -- @param devid string containing the device id to which the message should - -- be sent - -- @param subject string containing the messsage subject - -- @param message string containing the message body - -- @param alarm boolean true if alarm should be sounded, false if not - -- @return status true on success, false on failure - -- @return err string containing the error message (if status is false) - sendMessage = function(self, devid, subject, message, alarm) - local data = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true},"sound":%s,"subject":"%s","text":"%s"}' - data = data:format(devid, devid, tostring(alarm), subject, message) + -- Sends a message to an iOS device + -- @param devid string containing the device id to which the message should + -- be sent + -- @param subject string containing the messsage subject + -- @param message string containing the message body + -- @param alarm boolean true if alarm should be sounded, false if not + -- @return status true on success, false on failure + -- @return err string containing the error message (if status is false) + sendMessage = function(self, devid, subject, message, alarm) + local data = '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":5911,"osVersion":"3.2","productType":"iPad1,1","selectedDevice":"%s","shouldLocate":false},"device":"%s","serverContext":{"callbackIntervalInMS":3000,"clientId":"0000000000000000000000000000000000000000","deviceLoadStatus":"203","hasDevices":true,"lastSessionExtensionTime":null,"maxDeviceLoadTime":60000,"maxLocatingTime":90000,"preferredLanguage":"en","prefsUpdateTime":1276872996660,"sessionLifespan":900000,"timezone":{"currentOffset":-25200000,"previousOffset":-28800000,"previousTransition":1268560799999,"tzCurrentName":"Pacific Daylight Time","tzName":"America/Los_Angeles"},"validRegion":true},"sound":%s,"subject":"%s","text":"%s"}' + data = data:format(devid, devid, tostring(alarm), subject, message) - local url = ("/fmipservice/device/%s/sendMessage"):format(self.username) - local auth = { username = self.username, password = self.password } + local url = ("/fmipservice/device/%s/sendMessage"):format(self.username) + local auth = { username = self.username, password = self.password } - local response = http.post(self.host, self.port, url, { header = self.headers, auth = auth, timeout = 10000 }, nil, data) + local response = http.post(self.host, self.port, url, { header = self.headers, auth = auth, timeout = 10000 }, nil, data) - if ( response.status == 200 ) then - local status, resp = json.parse(response.body) - if ( not(status) ) then - stdnse.print_debug(2, "Failed to parse JSON response from server") - return false, "Failed to parse JSON response from server" - end + if ( response.status == 200 ) then + local status, resp = json.parse(response.body) + if ( not(status) ) then + stdnse.print_debug(2, "Failed to parse JSON response from server") + return false, "Failed to parse JSON response from server" + end - if ( resp.statusCode ~= "200" ) then - stdnse.print_debug(2, "Failed to send message to server") - return false, "Failed to send message to server" - end - end - return true - end, + if ( resp.statusCode ~= "200" ) then + stdnse.print_debug(2, "Failed to send message to server") + return false, "Failed to send message to server" + end + end + return true + end, - -- Updates location information for all devices controlled by the Apple ID - -- @return status true on success, false on failure - -- @return json parsed json table or string containing an error message on - -- failure - update = function(self) + -- Updates location information for all devices controlled by the Apple ID + -- @return status true on success, false on failure + -- @return json parsed json table or string containing an error message on + -- failure + update = function(self) - local auth = { - username = self.username, - password = self.password - } + local auth = { + username = self.username, + password = self.password + } - local url = ("/fmipservice/device/%s/initClient"):format(self.username) - local data= '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}' + local url = ("/fmipservice/device/%s/initClient"):format(self.username) + local data= '{"clientContext":{"appName":"FindMyiPhone","appVersion":"1.3","buildVersion":"145","deviceUDID":"0000000000000000000000000000000000000000","inactiveTime":2147483647,"osVersion":"4.2.1","personID":0,"productType":"iPad1,1"}}' - local retries = 2 + local retries = 2 - local response - repeat - response = http.post(self.host, self.port, url, { header = self.headers, auth = auth }, nil, data) - if ( response.header["x-apple-mme-host"] ) then - self.host = response.header["x-apple-mme-host"] - end + local response + repeat + response = http.post(self.host, self.port, url, { header = self.headers, auth = auth }, nil, data) + if ( response.header["x-apple-mme-host"] ) then + self.host = response.header["x-apple-mme-host"] + end - if ( response.status == 401 ) then - return false, "Authentication failed" - elseif ( response.status ~= 200 and response.status ~= 330 ) then - return false, "An unexpected error occured" - end + if ( response.status == 401 ) then + return false, "Authentication failed" + elseif ( response.status ~= 200 and response.status ~= 330 ) then + return false, "An unexpected error occured" + end - retries = retries - 1 - until ( 200 == response.status or 0 == retries) + retries = retries - 1 + until ( 200 == response.status or 0 == retries) - if ( response.status ~= 200 ) then - return false, "Received unexpected response from server" - end + if ( response.status ~= 200 ) then + return false, "Received unexpected response from server" + end - local status, parsed_json = json.parse(response.body) + local status, parsed_json = json.parse(response.body) - if ( not(status) or parsed_json.statusCode ~= "200" ) then - return false, "Failed to parse JSON response from server" - end + if ( not(status) or parsed_json.statusCode ~= "200" ) then + return false, "Failed to parse JSON response from server" + end - -- cache the parsed_json.content as devices - self.devices = parsed_json.content + -- cache the parsed_json.content as devices + self.devices = parsed_json.content - return true, parsed_json - end, + return true, parsed_json + end, - -- Get's a list of devices - -- @return devices table containing a list of devices - getDevices = function(self) - if ( not(self.devices) ) then - self:update() - end - return self.devices - end + -- Get's a list of devices + -- @return devices table containing a list of devices + getDevices = function(self) + if ( not(self.devices) ) then + self:update() + end + return self.devices + end } Helper = { - -- Creates a Helper instance - -- @param username string containing the Apple ID username - -- @param password string containing the Apple ID password - -- @return o new instance of Helper - new = function(self, username, password) - local o = { - mm = MobileMe:new(username, password) - } - setmetatable(o, self) - self.__index = self - o.mm:update() - return o - end, + -- Creates a Helper instance + -- @param username string containing the Apple ID username + -- @param password string containing the Apple ID password + -- @return o new instance of Helper + new = function(self, username, password) + local o = { + mm = MobileMe:new(username, password) + } + setmetatable(o, self) + self.__index = self + o.mm:update() + return o + end, - -- Get's the geolocation from each device - -- - -- @return status true on success, false on failure - -- @return result table containing a table of device locations - -- the table is indexed based on the name of the device and - -- contains a location table with the following fields: - -- * longitude - the GPS longitude - -- * latitude - the GPS latitude - -- * accuracy - the location accuracy - -- * timestamp - the time the location was acquired - -- * postype - the position type (GPS or WiFi) - -- * finished - - -- or string containing an error message on failure - getLocation = function(self) - -- do 3 tries, with a 5 second timeout to allow the location to update - -- there are two attributes, locationFinished and isLocating that seem - -- to be good candidates to monitor, but so far, I haven't had any - -- success with that. - local tries, timeout = 3, 5 - local result = {} + -- Get's the geolocation from each device + -- + -- @return status true on success, false on failure + -- @return result table containing a table of device locations + -- the table is indexed based on the name of the device and + -- contains a location table with the following fields: + -- * longitude - the GPS longitude + -- * latitude - the GPS latitude + -- * accuracy - the location accuracy + -- * timestamp - the time the location was acquired + -- * postype - the position type (GPS or WiFi) + -- * finished - + -- or string containing an error message on failure + getLocation = function(self) + -- do 3 tries, with a 5 second timeout to allow the location to update + -- there are two attributes, locationFinished and isLocating that seem + -- to be good candidates to monitor, but so far, I haven't had any + -- success with that. + local tries, timeout = 3, 5 + local result = {} - repeat - local status, response = self.mm:update() + repeat + local status, response = self.mm:update() - if ( not(status) or not(response) ) then - return false, "Failed to retrieve response from server" - end - for _, device in ipairs(response.content) do - if ( device.location ) then - result[device.name] = { - longitude = device.location.longitude, - latitude = device.location.latitude, - accuracy = device.location.horizontalAccuracy, - timestamp = device.location.timeStamp, - postype = device.location.positionType, - finished = device.location.locationFinished, - } - end - end - tries = tries - 1 - if ( tries > 0 ) then - stdnse.sleep(timeout) - end - until( tries == 0 ) - return true, result - end, + if ( not(status) or not(response) ) then + return false, "Failed to retrieve response from server" + end + for _, device in ipairs(response.content) do + if ( device.location ) then + result[device.name] = { + longitude = device.location.longitude, + latitude = device.location.latitude, + accuracy = device.location.horizontalAccuracy, + timestamp = device.location.timeStamp, + postype = device.location.positionType, + finished = device.location.locationFinished, + } + end + end + tries = tries - 1 + if ( tries > 0 ) then + stdnse.sleep(timeout) + end + until( tries == 0 ) + return true, result + end, - -- Gets a list of names and ids of devices associated with the Apple ID - -- @return status true on success, false on failure - -- @return table of devices containing the following fields: - -- name and id - getDevices = function(self) - local devices = {} - for _, dev in ipairs(self.mm:getDevices()) do - table.insert(devices, { name = dev.name, id = dev.id }) - end - return true, devices - end, + -- Gets a list of names and ids of devices associated with the Apple ID + -- @return status true on success, false on failure + -- @return table of devices containing the following fields: + -- name and id + getDevices = function(self) + local devices = {} + for _, dev in ipairs(self.mm:getDevices()) do + table.insert(devices, { name = dev.name, id = dev.id }) + end + return true, devices + end, - -- Send a message to an iOS Device - -- - -- @param devid string containing the device id to which the message should - -- be sent - -- @param subject string containing the messsage subject - -- @param message string containing the message body - -- @param alarm boolean true if alarm should be sounded, false if not - -- @return status true on success, false on failure - -- @return err string containing the error message (if status is false) - sendMessage = function(self, ...) - return self.mm:sendMessage(...) - end + -- Send a message to an iOS Device + -- + -- @param devid string containing the device id to which the message should + -- be sent + -- @param subject string containing the messsage subject + -- @param message string containing the message body + -- @param alarm boolean true if alarm should be sounded, false if not + -- @return status true on success, false on failure + -- @return err string containing the error message (if status is false) + sendMessage = function(self, ...) + return self.mm:sendMessage(...) + end } diff --git a/nselib/natpmp.lua b/nselib/natpmp.lua index 74540d902..088dbde94 100644 --- a/nselib/natpmp.lua +++ b/nselib/natpmp.lua @@ -13,129 +13,129 @@ local stdnse = require "stdnse" _ENV = stdnse.module("natpmp", stdnse.seeall) local ResultCode = { - SUCCESS = 0, - UNSUPPORTED_VERSION = 1, - NOT_AUTHORIZED = 2, - NETWORK_FAILURE = 3, - OUT_OF_RESOURCES = 4, - UNSUPPORTED_OPCODE = 5, + SUCCESS = 0, + UNSUPPORTED_VERSION = 1, + NOT_AUTHORIZED = 2, + NETWORK_FAILURE = 3, + OUT_OF_RESOURCES = 4, + UNSUPPORTED_OPCODE = 5, } local ErrorMessage = { - [ResultCode.UNSUPPORTED_VERSION] = "The device did not support the protocol version", - [ResultCode.NOT_AUTHORIZED] = "The operation was not authorized", - [ResultCode.NETWORK_FAILURE] = "Network failure", - [ResultCode.OUT_OF_RESOURCES] = "The device is out of resources", - [ResultCode.UNSUPPORTED_OPCODE] = "The requested operation was not supported", + [ResultCode.UNSUPPORTED_VERSION] = "The device did not support the protocol version", + [ResultCode.NOT_AUTHORIZED] = "The operation was not authorized", + [ResultCode.NETWORK_FAILURE] = "Network failure", + [ResultCode.OUT_OF_RESOURCES] = "The device is out of resources", + [ResultCode.UNSUPPORTED_OPCODE] = "The requested operation was not supported", } Request = { - GetWANIP = { + GetWANIP = { - new = function(self) - local o = { version = 0, op = 0 } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { version = 0, op = 0 } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return bin.pack(">CC", self.version, self.op) - end, + __tostring = function(self) + return bin.pack(">CC", self.version, self.op) + end, - }, + }, - MapPort = { + MapPort = { - new = function(self, pubport, privport, proto, lifetime) - assert(proto == "udp" or proto == "tcp", "Unsupported protocol") - local o = { - version = 0, - pubport = pubport, - privport = privport, - proto = proto, - lifetime = lifetime or 3600 - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, pubport, privport, proto, lifetime) + assert(proto == "udp" or proto == "tcp", "Unsupported protocol") + local o = { + version = 0, + pubport = pubport, + privport = privport, + proto = proto, + lifetime = lifetime or 3600 + } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return bin.pack(">CCSSSI", - self.version, - (self.proto=="udp" and 1 or 2), - 0, -- reserved - self.privport, self.pubport, - self.lifetime) - end, + __tostring = function(self) + return bin.pack(">CCSSSI", + self.version, + (self.proto=="udp" and 1 or 2), + 0, -- reserved + self.privport, self.pubport, + self.lifetime) + end, - } + } } Response = { - GetWANIP = { + GetWANIP = { - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - parse = function(self) - if ( #self.data ~= 12 ) then - return - end + parse = function(self) + if ( #self.data ~= 12 ) then + return + end - local pos - pos, self.version, self.op, self.rescode = bin.unpack("ISSI", self.data, pos) - return true - end, - } + pos, self.time, self.privport, self.pubport, self.lifetime = bin.unpack(">ISSI", self.data, pos) + return true + end, + } } @@ -146,75 +146,75 @@ Response = { Helper = { - new = function(self, host, port) - local o = { host = host, port = port } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, host, port) + local o = { host = host, port = port } + setmetatable(o, self) + self.__index = self + return o + end, - exchPacket = function(self, data) - local socket = nmap.new_socket("udp") - socket:set_timeout(5000) + exchPacket = function(self, data) + local socket = nmap.new_socket("udp") + socket:set_timeout(5000) - local status = socket:sendto(self.host, self.port, data) - if ( not(status) ) then - socket:close() - return false, "Failed to send request to device" - end + local status = socket:sendto(self.host, self.port, data) + if ( not(status) ) then + socket:close() + return false, "Failed to send request to device" + end - local response - status, response = socket:receive() - socket:close() - if ( not(status) ) then - return false, "Failed to receive response from router" - end - return true, response - end, + local response + status, response = socket:receive() + socket:close() + if ( not(status) ) then + return false, "Failed to receive response from router" + end + return true, response + end, - --- Gets the WAN ip of the router - getWANIP = function(self) - local packet = Request.GetWANIP:new() - local status, response = self:exchPacket(tostring(packet)) - if ( not(status) ) then - return status, response - end + --- Gets the WAN ip of the router + getWANIP = function(self) + local packet = Request.GetWANIP:new() + local status, response = self:exchPacket(tostring(packet)) + if ( not(status) ) then + return status, response + end - response = Response.GetWANIP:new(response) - if ( not(response) ) then - return false, "Failed to parse response from router" - end + response = Response.GetWANIP:new(response) + if ( not(response) ) then + return false, "Failed to parse response from router" + end - return true, response - end, + return true, response + end, - --- Maps a public port to a private port - -- @param pubport number containing the public external port to map - -- @param privport number containing the private internal port to map - -- @param protocol string containing the protocol to map (udp|tcp) - -- @param lifetime [optional] number containing the lifetime in seconds - mapPort = function(self, pubport, privport, protocol, lifetime) - local packet = Request.MapPort:new(pubport, privport, protocol, lifetime) - local status, response = self:exchPacket(tostring(packet)) - if ( not(status) ) then - return status, response - end + --- Maps a public port to a private port + -- @param pubport number containing the public external port to map + -- @param privport number containing the private internal port to map + -- @param protocol string containing the protocol to map (udp|tcp) + -- @param lifetime [optional] number containing the lifetime in seconds + mapPort = function(self, pubport, privport, protocol, lifetime) + local packet = Request.MapPort:new(pubport, privport, protocol, lifetime) + local status, response = self:exchPacket(tostring(packet)) + if ( not(status) ) then + return status, response + end - response = Response.MapPort:new(response) - if ( not(response) ) then - return false, "Failed to parse response from router" - end + response = Response.MapPort:new(response) + if ( not(response) ) then + return false, "Failed to parse response from router" + end - return true, response - end, + return true, response + end, - unmapPort = function(self, pubport, privport) - return self:mapPort(pubport, privport, 0) - end, + unmapPort = function(self, pubport, privport) + return self:mapPort(pubport, privport, 0) + end, - unmapAllPorts = function(self) - return self.mapPort(0, 0, 0) - end, + unmapAllPorts = function(self) + return self.mapPort(0, 0, 0) + end, } diff --git a/nselib/ndmp.lua b/nselib/ndmp.lua index be9a95161..c7911cd58 100644 --- a/nselib/ndmp.lua +++ b/nselib/ndmp.lua @@ -15,397 +15,397 @@ _ENV = stdnse.module("ndmp", stdnse.seeall) NDMP = { - -- Message types - MessageType = { - CONFIG_GET_HOST_INFO = 0x00000100, - CONFIG_GET_FS_INFO = 0x00000105, - CONFIG_GET_AUTH_ATTR = 0x00000103, - CONFIG_GET_SERVER_INFO = 0x00000108, - CONNECT_CLIENT_AUTH = 0x00000901, - }, + -- Message types + MessageType = { + CONFIG_GET_HOST_INFO = 0x00000100, + CONFIG_GET_FS_INFO = 0x00000105, + CONFIG_GET_AUTH_ATTR = 0x00000103, + CONFIG_GET_SERVER_INFO = 0x00000108, + CONNECT_CLIENT_AUTH = 0x00000901, + }, - -- The fragment header, 4 bytes where the highest bit is used to determine - -- whether the fragment is the last or not. - FragmentHeader = { - size = 4, + -- The fragment header, 4 bytes where the highest bit is used to determine + -- whether the fragment is the last or not. + FragmentHeader = { + size = 4, - -- Creates a new instance of fragment header - -- @return o instance of Class - new = function(self) - local o = { - last = true, - length = 24, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of fragment header + -- @return o instance of Class + new = function(self) + local o = { + last = true, + length = 24, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Parse data stream and create a new instance of this class - -- @param data opaque string - -- @return fh new instance of FragmentHeader class - parse = function(data) - local fh = NDMP.FragmentHeader:new() - local _, tmp = bin.unpack(">I", data) - fh.length = bit.band(tmp, 0x7fffffff) - fh.last= bit.rshift(tmp, 31) - return fh - end, + -- Parse data stream and create a new instance of this class + -- @param data opaque string + -- @return fh new instance of FragmentHeader class + parse = function(data) + local fh = NDMP.FragmentHeader:new() + local _, tmp = bin.unpack(">I", data) + fh.length = bit.band(tmp, 0x7fffffff) + fh.last= bit.rshift(tmp, 31) + return fh + end, - -- Serializes the instance to an opaque string - -- @return str string containing the serialized class - __tostring = function(self) - local tmp = 0 - if ( self.last ) then - tmp = 0x80000000 - end - tmp = tmp + self.length - return bin.pack(">I", tmp) - end, + -- Serializes the instance to an opaque string + -- @return str string containing the serialized class + __tostring = function(self) + local tmp = 0 + if ( self.last ) then + tmp = 0x80000000 + end + tmp = tmp + self.length + return bin.pack(">I", tmp) + end, - }, + }, - -- The ndmp 24 byte header - Header = { - size = 24, + -- The ndmp 24 byte header + Header = { + size = 24, - -- creates a new instance of Header - -- @return o instance of Header - new = function(self) - local o = { - seq = 0, - time = os.time(), - type = 0, - msg = 0x00000108, - reply_seq = 0, - error = 0, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- creates a new instance of Header + -- @return o instance of Header + new = function(self) + local o = { + seq = 0, + time = os.time(), + type = 0, + msg = 0x00000108, + reply_seq = 0, + error = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Create a Header instance from opaque data string - -- @param data opaque string - -- @return hdr new instance of Header - parse = function(data) - local hdr = NDMP.Header:new() - local pos - pos, hdr.seq, hdr.time, hdr.type, hdr.msg, hdr.reply_seq, hdr.error = bin.unpack(">IIIIII", data) - return hdr - end, + -- Create a Header instance from opaque data string + -- @param data opaque string + -- @return hdr new instance of Header + parse = function(data) + local hdr = NDMP.Header:new() + local pos + pos, hdr.seq, hdr.time, hdr.type, hdr.msg, hdr.reply_seq, hdr.error = bin.unpack(">IIIIII", data) + return hdr + end, - -- Serializes the instance to an opaque string - -- @return str string containing the serialized class instance - __tostring = function(self) - return bin.pack(">IIIIII", self.seq, self.time, self.type, self.msg, self.reply_seq, self.error) - end, + -- Serializes the instance to an opaque string + -- @return str string containing the serialized class instance + __tostring = function(self) + return bin.pack(">IIIIII", self.seq, self.time, self.type, self.msg, self.reply_seq, self.error) + end, - }, + }, } NDMP.Message = {} NDMP.Message.ConfigGetServerInfo = { - -- Creates a Config Server Info instance - -- @return o new instance of Class - new = function(self) - local o = { - frag_header = NDMP.FragmentHeader:new(), - header = NDMP.Header:new(), - data = nil, - } - o.header.msg = NDMP.MessageType.CONFIG_GET_SERVER_INFO - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a Config Server Info instance + -- @return o new instance of Class + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + } + o.header.msg = NDMP.MessageType.CONFIG_GET_SERVER_INFO + setmetatable(o, self) + self.__index = self + return o + end, - -- Create a ConfigGetServerInfo instance from opaque data string - -- @param data opaque string - -- @return msg new instance of ConfigGetServerInfo - parse = function(data) - local msg = NDMP.Message.ConfigGetServerInfo:new() - msg.frag_header = NDMP.FragmentHeader.parse(data) - data = data:sub(NDMP.FragmentHeader.size + 1) - msg.header = NDMP.Header.parse(data) - msg.data = data:sub(NDMP.Header.size + 1) + -- Create a ConfigGetServerInfo instance from opaque data string + -- @param data opaque string + -- @return msg new instance of ConfigGetServerInfo + parse = function(data) + local msg = NDMP.Message.ConfigGetServerInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) - msg.serverinfo = {} - local pos, err = bin.unpack(">I", msg.data) - pos, msg.serverinfo.vendor = Util.parseString(msg.data, pos) - pos, msg.serverinfo.product = Util.parseString(msg.data, pos) - pos, msg.serverinfo.version = Util.parseString(msg.data, pos) - return msg - end, + msg.serverinfo = {} + local pos, err = bin.unpack(">I", msg.data) + pos, msg.serverinfo.vendor = Util.parseString(msg.data, pos) + pos, msg.serverinfo.product = Util.parseString(msg.data, pos) + pos, msg.serverinfo.version = Util.parseString(msg.data, pos) + return msg + end, - -- Serializes the instance to an opaque string - -- @return str string containing the serialized class instance - __tostring = function(self) - return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") - end, + -- Serializes the instance to an opaque string + -- @return str string containing the serialized class instance + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end, } NDMP.Message.ConfigGetHostInfo = { - new = function(self) - local o = { - frag_header = NDMP.FragmentHeader:new(), - header = NDMP.Header:new(), - data = nil, - } - o.header.msg = NDMP.MessageType.CONFIG_GET_HOST_INFO - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + } + o.header.msg = NDMP.MessageType.CONFIG_GET_HOST_INFO + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local msg = NDMP.Message.ConfigGetServerInfo:new() - msg.frag_header = NDMP.FragmentHeader.parse(data) - data = data:sub(NDMP.FragmentHeader.size + 1) - msg.header = NDMP.Header.parse(data) - msg.data = data:sub(NDMP.Header.size + 1) + parse = function(data) + local msg = NDMP.Message.ConfigGetServerInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) - msg.hostinfo = {} - local pos, err = bin.unpack(">I", msg.data) - pos, msg.hostinfo.hostname = Util.parseString(msg.data, pos) - pos, msg.hostinfo.ostype = Util.parseString(msg.data, pos) - pos, msg.hostinfo.osver = Util.parseString(msg.data, pos) - pos, msg.hostinfo.hostid = Util.parseString(msg.data, pos) - return msg - end, + msg.hostinfo = {} + local pos, err = bin.unpack(">I", msg.data) + pos, msg.hostinfo.hostname = Util.parseString(msg.data, pos) + pos, msg.hostinfo.ostype = Util.parseString(msg.data, pos) + pos, msg.hostinfo.osver = Util.parseString(msg.data, pos) + pos, msg.hostinfo.hostid = Util.parseString(msg.data, pos) + return msg + end, - __tostring = function(self) - return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") - end, + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end, } NDMP.Message.ConfigGetFsInfo = { - new = function(self) - local o = { - frag_header = NDMP.FragmentHeader:new(), - header = NDMP.Header:new(), - data = nil, - fsinfo = {}, - } - o.header.msg = NDMP.MessageType.CONFIG_GET_FS_INFO - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + fsinfo = {}, + } + o.header.msg = NDMP.MessageType.CONFIG_GET_FS_INFO + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local msg = NDMP.Message.ConfigGetFsInfo:new() - msg.frag_header = NDMP.FragmentHeader.parse(data) - data = data:sub(NDMP.FragmentHeader.size + 1) - msg.header = NDMP.Header.parse(data) - msg.data = data:sub(NDMP.Header.size + 1) + parse = function(data) + local msg = NDMP.Message.ConfigGetFsInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) - local pos, err, count = bin.unpack(">II", msg.data) - for i=1, count do - local item = {} - pos, item.invalid = bin.unpack(">I", msg.data, pos) - pos, item.fs_type = Util.parseString(msg.data, pos) - pos, item.fs_logical_device = Util.parseString(msg.data, pos) - pos, item.fs_physical_device = Util.parseString(msg.data, pos) - pos, item.total_size = bin.unpack(">L", msg.data, pos) - pos, item.used_size = bin.unpack(">L", msg.data, pos) - pos, item.avail_size = bin.unpack(">L", msg.data, pos) - pos, item.total_inodes = bin.unpack(">L", msg.data, pos) - pos, item.used_inodes = bin.unpack(">L", msg.data, pos) - pos, item.fs_env = Util.parseString(msg.data, pos) - pos, item.fs_status = Util.parseString(msg.data, pos) - table.insert(msg.fsinfo, item) - end - return msg - end, + local pos, err, count = bin.unpack(">II", msg.data) + for i=1, count do + local item = {} + pos, item.invalid = bin.unpack(">I", msg.data, pos) + pos, item.fs_type = Util.parseString(msg.data, pos) + pos, item.fs_logical_device = Util.parseString(msg.data, pos) + pos, item.fs_physical_device = Util.parseString(msg.data, pos) + pos, item.total_size = bin.unpack(">L", msg.data, pos) + pos, item.used_size = bin.unpack(">L", msg.data, pos) + pos, item.avail_size = bin.unpack(">L", msg.data, pos) + pos, item.total_inodes = bin.unpack(">L", msg.data, pos) + pos, item.used_inodes = bin.unpack(">L", msg.data, pos) + pos, item.fs_env = Util.parseString(msg.data, pos) + pos, item.fs_status = Util.parseString(msg.data, pos) + table.insert(msg.fsinfo, item) + end + return msg + end, - __tostring = function(self) - return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") - end, + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end, } NDMP.Message.UnhandledMessage = { - new = function(self) - local o = { - frag_header = NDMP.FragmentHeader:new(), - header = NDMP.Header:new(), - data = nil, - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { + frag_header = NDMP.FragmentHeader:new(), + header = NDMP.Header:new(), + data = nil, + } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local msg = NDMP.Message.ConfigGetFsInfo:new() - msg.frag_header = NDMP.FragmentHeader.parse(data) - data = data:sub(NDMP.FragmentHeader.size + 1) - msg.header = NDMP.Header.parse(data) - msg.data = data:sub(NDMP.Header.size + 1) - return msg - end, + parse = function(data) + local msg = NDMP.Message.ConfigGetFsInfo:new() + msg.frag_header = NDMP.FragmentHeader.parse(data) + data = data:sub(NDMP.FragmentHeader.size + 1) + msg.header = NDMP.Header.parse(data) + msg.data = data:sub(NDMP.Header.size + 1) + return msg + end, - __tostring = function(self) - return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") - end + __tostring = function(self) + return tostring(self.frag_header) .. tostring(self.header) .. tostring(self.data or "") + end } Util = { - parseString = function(data, pos) - local pos, str = bin.unpack(">a", data, pos) - local pad = ( 4 - ( #str % 4 ) ~= 4 ) and 4 - ( #str % 4 ) or 0 - return pos + pad, str + parseString = function(data, pos) + local pos, str = bin.unpack(">a", data, pos) + local pad = ( 4 - ( #str % 4 ) ~= 4 ) and 4 - ( #str % 4 ) or 0 + return pos + pad, str - end, + end, } NDMP.TypeToMessage = { - [NDMP.MessageType.CONFIG_GET_SERVER_INFO] = NDMP.Message.ConfigGetServerInfo, - [NDMP.MessageType.CONFIG_GET_HOST_INFO] = NDMP.Message.ConfigGetHostInfo, - [NDMP.MessageType.CONFIG_GET_FS_INFO] = NDMP.Message.ConfigGetFsInfo, + [NDMP.MessageType.CONFIG_GET_SERVER_INFO] = NDMP.Message.ConfigGetServerInfo, + [NDMP.MessageType.CONFIG_GET_HOST_INFO] = NDMP.Message.ConfigGetHostInfo, + [NDMP.MessageType.CONFIG_GET_FS_INFO] = NDMP.Message.ConfigGetFsInfo, } -- Handles the communication with the NDMP service Comm = { - -- Creates new Comm instance - -- @param host table as received by the action method - -- @param port table as receuved by the action method - -- @return o new instance of Comm - new = function(self, host, port) - local o = { - host = host, - port = port, - socket = nmap.new_socket(), - seq = 0, - in_queue = {}, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates new Comm instance + -- @param host table as received by the action method + -- @param port table as receuved by the action method + -- @return o new instance of Comm + new = function(self, host, port) + local o = { + host = host, + port = port, + socket = nmap.new_socket(), + seq = 0, + in_queue = {}, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects to the NDMP server - -- @return status true on success, false on failure - connect = function(self) - -- some servers seem to take their time, so leave this as 10s for now - self.socket:set_timeout(10000) - return self.socket:connect(self.host, self.port) - end, + -- Connects to the NDMP server + -- @return status true on success, false on failure + connect = function(self) + -- some servers seem to take their time, so leave this as 10s for now + self.socket:set_timeout(10000) + return self.socket:connect(self.host, self.port) + end, - -- Receives a message from the server - -- @return status true on success, false on failure - -- @return msg NDMP message when a parser exists, otherwise nil - sock_recv = function(self) - local status, frag_data = self.socket:receive_buf(match.numbytes(4), true) - if ( not(status) ) then - return false, "Failed to read NDMP 4-byte fragment header" - end - local frag_header = NDMP.FragmentHeader.parse(frag_data) + -- Receives a message from the server + -- @return status true on success, false on failure + -- @return msg NDMP message when a parser exists, otherwise nil + sock_recv = function(self) + local status, frag_data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read NDMP 4-byte fragment header" + end + local frag_header = NDMP.FragmentHeader.parse(frag_data) - local status, header_data = self.socket:receive_buf(match.numbytes(24), true) - if ( not(status) ) then - return false, "Failed to read NDMP 24-byte header" - end - local header = NDMP.Header.parse(header_data) + local status, header_data = self.socket:receive_buf(match.numbytes(24), true) + if ( not(status) ) then + return false, "Failed to read NDMP 24-byte header" + end + local header = NDMP.Header.parse(header_data) - local status, data = self.socket:receive_buf(match.numbytes(frag_header.length - 24), true) - if ( not(status) ) then - return false, "Failed to read NDMP data" - end + local status, data = self.socket:receive_buf(match.numbytes(frag_header.length - 24), true) + if ( not(status) ) then + return false, "Failed to read NDMP data" + end - if ( NDMP.TypeToMessage[header.msg] ) then - return true, NDMP.TypeToMessage[header.msg].parse(frag_data .. header_data .. data) - end - return true, NDMP.Message.UnhandledMessage.parse(frag_data .. header_data .. data) - end, + if ( NDMP.TypeToMessage[header.msg] ) then + return true, NDMP.TypeToMessage[header.msg].parse(frag_data .. header_data .. data) + end + return true, NDMP.Message.UnhandledMessage.parse(frag_data .. header_data .. data) + end, - recv = function(self) - if ( #self.in_queue > 0 ) then - return true, table.remove(self.in_queue, 1) - end - return self:sock_recv() - end, + recv = function(self) + if ( #self.in_queue > 0 ) then + return true, table.remove(self.in_queue, 1) + end + return self:sock_recv() + end, - -- Sends a message to the server - -- @param msg NDMP message - -- @return status true on success, false on failure - -- @return err string containing the error message when status is false - send = function(self, msg) - self.seq = self.seq + 1 - msg.header.seq = self.seq - return self.socket:send(tostring(msg)) - end, + -- Sends a message to the server + -- @param msg NDMP message + -- @return status true on success, false on failure + -- @return err string containing the error message when status is false + send = function(self, msg) + self.seq = self.seq + 1 + msg.header.seq = self.seq + return self.socket:send(tostring(msg)) + end, - exch = function(self, msg) - local status, err = self:send(msg) - if ( not(status) ) then - return false, "Failed to send ndmp Message to server" - end - local s_seq = msg.header.seq + exch = function(self, msg) + local status, err = self:send(msg) + if ( not(status) ) then + return false, "Failed to send ndmp Message to server" + end + local s_seq = msg.header.seq - for k, v in ipairs(self.in_queue) do - if ( v.reply_seq == s_seq ) then - return true, table.remove(self.in_queue, k) - end - end + for k, v in ipairs(self.in_queue) do + if ( v.reply_seq == s_seq ) then + return true, table.remove(self.in_queue, k) + end + end - while(true) do - local reply - status, reply = self:sock_recv() - if ( not(status) ) then - return false, "Failed to receive msg from server" - elseif ( reply and reply.header and reply.header.reply_seq == s_seq ) then - return true, reply - else - table.insert(self.in_queue, reply) - end - end - end, + while(true) do + local reply + status, reply = self:sock_recv() + if ( not(status) ) then + return false, "Failed to receive msg from server" + elseif ( reply and reply.header and reply.header.reply_seq == s_seq ) then + return true, reply + else + table.insert(self.in_queue, reply) + end + end + end, - close = function(self) return self.socket:close() end, + close = function(self) return self.socket:close() end, } Helper = { - new = function(self, host, port) - local o = { comm = Comm:new(host, port) } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, host, port) + local o = { comm = Comm:new(host, port) } + setmetatable(o, self) + self.__index = self + return o + end, - connect = function(self) - return self.comm:connect() - end, + connect = function(self) + return self.comm:connect() + end, - getFsInfo = function(self) - return self.comm:exch(NDMP.Message.ConfigGetFsInfo:new()) - end, + getFsInfo = function(self) + return self.comm:exch(NDMP.Message.ConfigGetFsInfo:new()) + end, - getHostInfo = function(self) - return self.comm:exch(NDMP.Message.ConfigGetHostInfo:new()) - end, + getHostInfo = function(self) + return self.comm:exch(NDMP.Message.ConfigGetHostInfo:new()) + end, - getServerInfo = function(self) - return self.comm:exch(NDMP.Message.ConfigGetServerInfo:new()) - end, + getServerInfo = function(self) + return self.comm:exch(NDMP.Message.ConfigGetServerInfo:new()) + end, - close = function(self) - return self.comm:close() + close = function(self) + return self.comm:close() - end + end } diff --git a/nselib/netbios.lua b/nselib/netbios.lua index 0412d58a2..40c4f9886 100644 --- a/nselib/netbios.lua +++ b/nselib/netbios.lua @@ -17,8 +17,8 @@ _ENV = stdnse.module("netbios", stdnse.seeall) types = { - NB = 32, - NBSTAT = 33, + NB = 32, + NBSTAT = 33, } --- Encode a NetBIOS name for transport. Most packets that use the NetBIOS name @@ -39,45 +39,45 @@ types = { -- (eg. "\x20FEEFFDFEDBCACACACACACACACACAAA\x08insecure\x03org") function name_encode(name, scope) - stdnse.print_debug(3, "Encoding name '%s'", name) - -- Truncate or pad the string to 16 bytes - if(#name >= 16) then - name = string.sub(name, 1, 16) - else - local padding = " " - if name == "*" then - padding = "\0" - end + stdnse.print_debug(3, "Encoding name '%s'", name) + -- Truncate or pad the string to 16 bytes + if(#name >= 16) then + name = string.sub(name, 1, 16) + else + local padding = " " + if name == "*" then + padding = "\0" + end - repeat - name = name .. padding - until #name == 16 - end + repeat + name = name .. padding + until #name == 16 + end - -- Convert to uppercase - name = string.upper(name) + -- Convert to uppercase + name = string.upper(name) - -- Do the L1 encoding - local L1_encoded = "" - for i=1, #name, 1 do - local b = string.byte(name, i) - L1_encoded = L1_encoded .. string.char(bit.rshift(bit.band(b, 0xF0), 4) + 0x41) - L1_encoded = L1_encoded .. string.char(bit.rshift(bit.band(b, 0x0F), 0) + 0x41) - end + -- Do the L1 encoding + local L1_encoded = "" + for i=1, #name, 1 do + local b = string.byte(name, i) + L1_encoded = L1_encoded .. string.char(bit.rshift(bit.band(b, 0xF0), 4) + 0x41) + L1_encoded = L1_encoded .. string.char(bit.rshift(bit.band(b, 0x0F), 0) + 0x41) + end - -- Do the L2 encoding - local L2_encoded = string.char(32) .. L1_encoded + -- Do the L2 encoding + local L2_encoded = string.char(32) .. L1_encoded - if scope ~= nil then - -- Split the scope at its periods - local piece - for piece in string.gmatch(scope, "[^.]+") do - L2_encoded = L2_encoded .. string.char(#piece) .. piece - end - end + if scope ~= nil then + -- Split the scope at its periods + local piece + for piece in string.gmatch(scope, "[^.]+") do + L2_encoded = L2_encoded .. string.char(#piece) .. piece + end + end - stdnse.print_debug(3, "=> '%s'", L2_encoded) - return L2_encoded + stdnse.print_debug(3, "=> '%s'", L2_encoded) + return L2_encoded end @@ -89,38 +89,38 @@ end --@return the decoded name and the scope. The name will still be padded, and the -- scope will never be nil (empty string is returned if no scope is present) function name_decode(encoded_name) - local name = "" - local scope = "" + local name = "" + local scope = "" - local len = string.byte(encoded_name, 1) - local i + local len = string.byte(encoded_name, 1) + local i - stdnse.print_debug(3, "Decoding name '%s'", encoded_name) + stdnse.print_debug(3, "Decoding name '%s'", encoded_name) - for i = 2, len + 1, 2 do - local ch = 0 - ch = bit.bor(ch, bit.lshift(string.byte(encoded_name, i) - 0x41, 4)) - ch = bit.bor(ch, bit.lshift(string.byte(encoded_name, i + 1) - 0x41, 0)) + for i = 2, len + 1, 2 do + local ch = 0 + ch = bit.bor(ch, bit.lshift(string.byte(encoded_name, i) - 0x41, 4)) + ch = bit.bor(ch, bit.lshift(string.byte(encoded_name, i + 1) - 0x41, 0)) - name = name .. string.char(ch) - end + name = name .. string.char(ch) + end - -- Decode the scope - local pos = 34 - while #encoded_name > pos do - local len = string.byte(encoded_name, pos) - scope = scope .. string.sub(encoded_name, pos + 1, pos + len) .. "." - pos = pos + 1 + len - end + -- Decode the scope + local pos = 34 + while #encoded_name > pos do + local len = string.byte(encoded_name, pos) + scope = scope .. string.sub(encoded_name, pos + 1, pos + len) .. "." + pos = pos + 1 + len + end - -- If there was a scope, remove the trailing period - if(#scope > 0) then - scope = string.sub(scope, 1, #scope - 1) - end + -- If there was a scope, remove the trailing period + if(#scope > 0) then + scope = string.sub(scope, 1, #scope - 1) + end - stdnse.print_debug(3, "=> '%s'", name) + stdnse.print_debug(3, "=> '%s'", name) - return name, scope + return name, scope end --- Sends out a UDP probe on port 137 to get a human-readable list of names the @@ -131,23 +131,23 @@ end -- list of names. Otherwise, result is an error message. function get_names(host, prefix) - local status, names, statistics = do_nbstat(host) + local status, names, statistics = do_nbstat(host) - if(prefix == nil) then - prefix = "" - end + if(prefix == nil) then + prefix = "" + end - if(status) then - local result = "" - for i = 1, #names, 1 do - result = result .. string.format("%s%s<%02x>\n", prefix, names[i]['name'], names[i]['prefix']) - end + if(status) then + local result = "" + for i = 1, #names, 1 do + result = result .. string.format("%s%s<%02x>\n", prefix, names[i]['name'], names[i]['prefix']) + end - return true, result - else - return false, names - end + return true, result + else + return false, names + end end --- Sends out a UDP probe on port 137 to get the server's name (that is, the @@ -158,24 +158,24 @@ end -- otherwise, result is an error message. function get_server_name(host, names) - local status - local i + local status + local i - if names == nil then - status, names = do_nbstat(host) + if names == nil then + status, names = do_nbstat(host) - if(status == false) then - return false, names - end - end + if(status == false) then + return false, names + end + end - for i = 1, #names, 1 do - if names[i]['suffix'] == 0x20 then - return true, names[i]['name'] - end - end + for i = 1, #names, 1 do + if names[i]['suffix'] == 0x20 then + return true, names[i]['name'] + end + end - return false, "Couldn't find NetBIOS server name" + return false, "Couldn't find NetBIOS server name" end --- Sends out a UDP probe on port 137 to get the user's name (that is, the @@ -188,27 +188,27 @@ end -- otherwise, result is an error message. function get_user_name(host, names) - local status, server_name = get_server_name(host, names) + local status, server_name = get_server_name(host, names) - if(status == false) then - return false, server_name - end + if(status == false) then + return false, server_name + end - if(names == nil) then - status, names = do_nbstat(host) + if(names == nil) then + status, names = do_nbstat(host) - if(status == false) then - return false, names - end - end + if(status == false) then + return false, names + end + end - for i = 1, #names, 1 do - if names[i]['suffix'] == 0x03 and names[i]['name'] ~= server_name then - return true, names[i]['name'] - end - end + for i = 1, #names, 1 do + if names[i]['suffix'] == 0x03 and names[i]['name'] ~= server_name then + return true, names[i]['name'] + end + end - return true, nil + return true, nil end @@ -260,204 +260,204 @@ end -- Otherwise, names is an error message and statistics is undefined. function do_nbstat(host) - local status, err - local socket = nmap.new_socket() - local encoded_name = name_encode("*") - local statistics - local reg - if type(host) == "string" then --ip - stdnse.print_debug(3, "Performing nbstat on host '%s'", host) - nmap.registry.netbios = nmap.registry.netbios or {} - nmap.registry.netbios[host] = nmap.registry.netbios[host] or {} - reg = nmap.registry.netbios[host] - else - stdnse.print_debug(3, "Performing nbstat on host '%s'", host.ip) - if host.registry.netbios == nil and - nmap.registry.netbios ~= nil and - nmap.registry.netbios[host.ip] ~= nil then - host.registry.netbios = nmap.registry.netbios[host.ip] - end - host.registry.netbios = host.registry.netbios or {} - reg = host.registry.netbios - end + local status, err + local socket = nmap.new_socket() + local encoded_name = name_encode("*") + local statistics + local reg + if type(host) == "string" then --ip + stdnse.print_debug(3, "Performing nbstat on host '%s'", host) + nmap.registry.netbios = nmap.registry.netbios or {} + nmap.registry.netbios[host] = nmap.registry.netbios[host] or {} + reg = nmap.registry.netbios[host] + else + stdnse.print_debug(3, "Performing nbstat on host '%s'", host.ip) + if host.registry.netbios == nil and + nmap.registry.netbios ~= nil and + nmap.registry.netbios[host.ip] ~= nil then + host.registry.netbios = nmap.registry.netbios[host.ip] + end + host.registry.netbios = host.registry.netbios or {} + reg = host.registry.netbios + end - -- Check if it's cached in the registry for this host - if(reg["nbstat_names"] ~= nil) then - stdnse.print_debug(3, " |_ [using cached value]") - return true, reg["nbstat_names"], reg["nbstat_statistics"] - end + -- Check if it's cached in the registry for this host + if(reg["nbstat_names"] ~= nil) then + stdnse.print_debug(3, " |_ [using cached value]") + return true, reg["nbstat_names"], reg["nbstat_statistics"] + end - -- Create the query header - local query = bin.pack(">SSSSSS", - 0x1337, -- Transaction id - 0x0000, -- Flags - 1, -- Questions - 0, -- Answers - 0, -- Authority - 0 -- Extra - ) + -- Create the query header + local query = bin.pack(">SSSSSS", + 0x1337, -- Transaction id + 0x0000, -- Flags + 1, -- Questions + 0, -- Answers + 0, -- Authority + 0 -- Extra + ) - query = query .. bin.pack(">zSS", - encoded_name, -- Encoded name - 0x0021, -- Query type (0x21 = NBSTAT) - 0x0001 -- Class = IN - ) - status, err = socket:connect(host, 137, "udp") - if(status == false) then - return false, err - end + query = query .. bin.pack(">zSS", + encoded_name, -- Encoded name + 0x0021, -- Query type (0x21 = NBSTAT) + 0x0001 -- Class = IN + ) + status, err = socket:connect(host, 137, "udp") + if(status == false) then + return false, err + end - status, err = socket:send(query) - if(status == false) then - return false, err - end + status, err = socket:send(query) + if(status == false) then + return false, err + end - socket:set_timeout(1000) + socket:set_timeout(1000) - local status, result = socket:receive_bytes(1) - if(status == false) then - return false, result - end + local status, result = socket:receive_bytes(1) + if(status == false) then + return false, result + end - local close_status, err = socket:close() - if(close_status == false) then - return false, err - end + local close_status, err = socket:close() + if(close_status == false) then + return false, err + end - if(status) then - local pos, TRN_ID, FLAGS, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT, rr_name, rr_type, rr_class, rr_ttl - local rrlength, name_count + if(status) then + local pos, TRN_ID, FLAGS, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT, rr_name, rr_type, rr_class, rr_ttl + local rrlength, name_count - pos, TRN_ID, FLAGS, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT = bin.unpack(">SSSSSS", result) + pos, TRN_ID, FLAGS, QDCOUNT, ANCOUNT, NSCOUNT, ARCOUNT = bin.unpack(">SSSSSS", result) - -- Sanity check the result (has to have the same TRN_ID, 1 answer, and proper flags) - if(TRN_ID ~= 0x1337) then - return false, string.format("Invalid transaction ID returned: 0x%04x", TRN_ID) - end - if(ANCOUNT ~= 1) then - return false, "Server returned an invalid number of answers" - end - if(bit.band(FLAGS, 0x8000) == 0) then - return false, "Server's flags didn't indicate a response" - end - if(bit.band(FLAGS, 0x0007) ~= 0) then - return false, string.format("Server returned a NetBIOS error: 0x%02x", bit.band(FLAGS, 0x0007)) - end + -- Sanity check the result (has to have the same TRN_ID, 1 answer, and proper flags) + if(TRN_ID ~= 0x1337) then + return false, string.format("Invalid transaction ID returned: 0x%04x", TRN_ID) + end + if(ANCOUNT ~= 1) then + return false, "Server returned an invalid number of answers" + end + if(bit.band(FLAGS, 0x8000) == 0) then + return false, "Server's flags didn't indicate a response" + end + if(bit.band(FLAGS, 0x0007) ~= 0) then + return false, string.format("Server returned a NetBIOS error: 0x%02x", bit.band(FLAGS, 0x0007)) + end - -- Start parsing the answer field - pos, rr_name, rr_type, rr_class, rr_ttl = bin.unpack(">zSSI", result, pos) + -- Start parsing the answer field + pos, rr_name, rr_type, rr_class, rr_ttl = bin.unpack(">zSSI", result, pos) - -- More sanity checks - if(rr_name ~= encoded_name) then - return false, "Server returned incorrect name" - end - if(rr_class ~= 0x0001) then - return false, "Server returned incorrect class" - end - if(rr_type ~= 0x0021) then - return false, "Server returned incorrect query type" - end + -- More sanity checks + if(rr_name ~= encoded_name) then + return false, "Server returned incorrect name" + end + if(rr_class ~= 0x0001) then + return false, "Server returned incorrect class" + end + if(rr_type ~= 0x0021) then + return false, "Server returned incorrect query type" + end - pos, rrlength, name_count = bin.unpack(">SC", result, pos) + pos, rrlength, name_count = bin.unpack(">SC", result, pos) - local names = {} - for i = 1, name_count do - local name, suffix, flags + local names = {} + for i = 1, name_count do + local name, suffix, flags - -- Instead of reading the 16-byte name and pulling off the suffix, - -- we read the first 15 bytes and then the 1-byte suffix. - pos, name, suffix, flags = bin.unpack(">A15CS", result, pos) - name = string.gsub(name, "[ ]*$", "") + -- Instead of reading the 16-byte name and pulling off the suffix, + -- we read the first 15 bytes and then the 1-byte suffix. + pos, name, suffix, flags = bin.unpack(">A15CS", result, pos) + name = string.gsub(name, "[ ]*$", "") - names[i] = {} - names[i]['name'] = name - names[i]['suffix'] = suffix - names[i]['flags'] = flags + names[i] = {} + names[i]['name'] = name + names[i]['suffix'] = suffix + names[i]['flags'] = flags - -- Decrement the length - rrlength = rrlength - 18 - end + -- Decrement the length + rrlength = rrlength - 18 + end - if(rrlength > 0) then - rrlength = rrlength - 1 - end - pos, statistics = bin.unpack(string.format(">A%d", rrlength), result, pos) + if(rrlength > 0) then + rrlength = rrlength - 1 + end + pos, statistics = bin.unpack(string.format(">A%d", rrlength), result, pos) - -- Put it in the registry, in case anybody else needs it - reg["nbstat_names"] = names - reg["nbstat_statistics"] = statistics + -- Put it in the registry, in case anybody else needs it + reg["nbstat_names"] = names + reg["nbstat_statistics"] = statistics - return true, names, statistics + return true, names, statistics - else - return false, "Name query failed: " .. result - end + else + return false, "Name query failed: " .. result + end end function nbquery(host, nbname, options) - -- override any options or set the default values - local options = options or {} - options.port = options.port or 137 - options.retPkt = options.retPkt or true - options.dtype = options.dtype or types.NB - options.host = host.ip - options.flags = options.flags or ( options.multiple and 0x0110 ) - options.id = math.random(0xFFFF) + -- override any options or set the default values + local options = options or {} + options.port = options.port or 137 + options.retPkt = options.retPkt or true + options.dtype = options.dtype or types.NB + options.host = host.ip + options.flags = options.flags or ( options.multiple and 0x0110 ) + options.id = math.random(0xFFFF) - -- encode and chop off the leading byte, as the dns library takes care of - -- specifying the length - local encoded_name = name_encode(nbname):sub(2) + -- encode and chop off the leading byte, as the dns library takes care of + -- specifying the length + local encoded_name = name_encode(nbname):sub(2) - local status, response = dns.query( encoded_name, options ) - if ( not(status) ) then return false, "ERROR: nbquery failed" end + local status, response = dns.query( encoded_name, options ) + if ( not(status) ) then return false, "ERROR: nbquery failed" end - local results = {} - -- discard any additional responses - if ( options.multiple and #response > 0 ) then - for _, resp in ipairs(response) do - assert( options.id == resp.output.id, "Received packet with invalid transaction ID" ) - if ( not(resp.output.answers) or #resp.output.answers < 1 ) then - return false, "ERROR: Response contained no answers" - end - local dname = string.char(#resp.output.answers[1].dname) .. resp.output.answers[1].dname - table.insert( results, { peer = resp.peer, name = name_decode(dname) } ) - end - return true, results - else - local dname = string.char(#response.answers[1].dname) .. response.answers[1].dname - return true, { { peer = host.ip, name = name_decode(dname) } } - end + local results = {} + -- discard any additional responses + if ( options.multiple and #response > 0 ) then + for _, resp in ipairs(response) do + assert( options.id == resp.output.id, "Received packet with invalid transaction ID" ) + if ( not(resp.output.answers) or #resp.output.answers < 1 ) then + return false, "ERROR: Response contained no answers" + end + local dname = string.char(#resp.output.answers[1].dname) .. resp.output.answers[1].dname + table.insert( results, { peer = resp.peer, name = name_decode(dname) } ) + end + return true, results + else + local dname = string.char(#response.answers[1].dname) .. response.answers[1].dname + return true, { { peer = host.ip, name = name_decode(dname) } } + end end ---Convert the 16-bit flags field to a string. --@param flags The 16-bit flags field --@return A string representing the flags function flags_to_string(flags) - local result = "" + local result = "" - if(bit.band(flags, 0x8000) ~= 0) then - result = result .. "" - else - result = result .. "" - end + if(bit.band(flags, 0x8000) ~= 0) then + result = result .. "" + else + result = result .. "" + end - if(bit.band(flags, 0x1000) ~= 0) then - result = result .. "" - end + if(bit.band(flags, 0x1000) ~= 0) then + result = result .. "" + end - if(bit.band(flags, 0x0800) ~= 0) then - result = result .. "" - end + if(bit.band(flags, 0x0800) ~= 0) then + result = result .. "" + end - if(bit.band(flags, 0x0400) ~= 0) then - result = result .. "" - end + if(bit.band(flags, 0x0400) ~= 0) then + result = result .. "" + end - if(bit.band(flags, 0x0200) ~= 0) then - result = result .. "" - end + if(bit.band(flags, 0x0200) ~= 0) then + result = result .. "" + end - return result + return result end diff --git a/nselib/nrpc.lua b/nselib/nrpc.lua index fb519cee6..badb5d0ec 100644 --- a/nselib/nrpc.lua +++ b/nselib/nrpc.lua @@ -9,12 +9,12 @@ -- -------- -- The library contains the following classes: -- --- o DominoPacket --- - The packet class holding the packets sent between the client and the +-- o DominoPacket +-- - The packet class holding the packets sent between the client and the -- IBM Lotus Domino server -- -- o Helper --- - A helper class that provides easy access to the rest of the library +-- - A helper class that provides easy access to the rest of the library -- -- o DominoSocket -- - This is a copy of the DB2Socket class which provides fundamental @@ -27,10 +27,10 @@ -- to interface the library: -- -- --- helper = nrpc.Helper:new(host, port) --- status, err = nrpc:Connect() --- status, res = nrpc:isValidUser("Patrik Karlsson") --- status, err = nrpc:Close() +-- helper = nrpc.Helper:new(host, port) +-- status, err = nrpc:Connect() +-- status, res = nrpc:isValidUser("Patrik Karlsson") +-- status, err = nrpc:Close() -- -- -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html @@ -51,180 +51,180 @@ _ENV = stdnse.module("nrpc", stdnse.seeall) -- The Domino Packet DominoPacket = { - --- Creates a new DominoPacket instance - -- - -- @param data string containing the packet data - -- @return a new DominoPacket instance - new = function( self, data ) - local o = {} - setmetatable(o, self) - self.__index = self - o.data = data - return o - end, + --- Creates a new DominoPacket instance + -- + -- @param data string containing the packet data + -- @return a new DominoPacket instance + new = function( self, data ) + local o = {} + setmetatable(o, self) + self.__index = self + o.data = data + return o + end, - --- Reads a packet from the DominoSocket - -- - -- @param domsock DominoSocket connected to the server - -- @return Status (true or false). - -- @return Error code (if status is false). - read = function( self, domsock ) - local status, data = domsock:recv(2) - local pos, len = bin.unpack( ""tcp", "udp", or - -- @return Status (true or false). - -- @return Error code (if status is false). - connect = function( self, hostid, port, protocol ) - self.Socket:set_timeout(5000) - return self.Socket:connect( hostid, port, protocol ) - end, + --- Establishes a connection. + -- + -- @param hostid Hostname or IP address. + -- @param port Port number. + -- @param protocol "tcp", "udp", or + -- @return Status (true or false). + -- @return Error code (if status is false). + connect = function( self, hostid, port, protocol ) + self.Socket:set_timeout(5000) + return self.Socket:connect( hostid, port, protocol ) + end, - --- Closes an open connection. - -- - -- @return Status (true or false). - -- @return Error code (if status is false). - close = function( self ) - return self.Socket:close() - end, + --- Closes an open connection. + -- + -- @return Status (true or false). + -- @return Error code (if status is false). + close = function( self ) + return self.Socket:close() + end, - --- Opposed to the socket:receive_bytes function, that returns - -- at least x bytes, this function returns the amount of bytes requested. - -- - -- @param count of bytes to read - -- @return true on success, false on failure - -- @return data containing bytes read from the socket - -- err containing error message if status is false - recv = function( self, count ) - local status, data + --- Opposed to the socket:receive_bytes function, that returns + -- at least x bytes, this function returns the amount of bytes requested. + -- + -- @param count of bytes to read + -- @return true on success, false on failure + -- @return data containing bytes read from the socket + -- err containing error message if status is false + recv = function( self, count ) + local status, data - self.Buffer = self.Buffer or "" + self.Buffer = self.Buffer or "" - if ( #self.Buffer < count ) then - status, data = self.Socket:receive_bytes( count - #self.Buffer ) - if ( not(status) or #data < count - #self.Buffer ) then - return false, data - end - self.Buffer = self.Buffer .. data - end + if ( #self.Buffer < count ) then + status, data = self.Socket:receive_bytes( count - #self.Buffer ) + if ( not(status) or #data < count - #self.Buffer ) then + return false, data + end + self.Buffer = self.Buffer .. data + end - data = self.Buffer:sub( 1, count ) - self.Buffer = self.Buffer:sub( count + 1) + data = self.Buffer:sub( 1, count ) + self.Buffer = self.Buffer:sub( count + 1) - return true, data - end, + return true, data + end, - --- Sends data over the socket - -- - -- @return Status (true or false). - -- @return Error code (if status is false). - send = function( self, data ) - return self.Socket:send( data ) - end, + --- Sends data over the socket + -- + -- @return Status (true or false). + -- @return Error code (if status is false). + send = function( self, data ) + return self.Socket:send( data ) + end, } Helper = { - --- Creates a new Helper instance - -- - -- @param host table as recieved by the script action method - -- @param port table as recieved by the script action method - new = function(self, host, port) - local o = {} - setmetatable(o, self) - self.__index = self - o.host = host - o.port = port - o.domsock = DominoSocket:new() - return o - end, + --- Creates a new Helper instance + -- + -- @param host table as recieved by the script action method + -- @param port table as recieved by the script action method + new = function(self, host, port) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.domsock = DominoSocket:new() + return o + end, - --- Connects the socket to the Domino server - -- - -- @return status true on success, false on failure - -- @return err error message if status is false - connect = function( self ) - if( not( self.domsock:connect( self.host.ip, self.port.number, "tcp" ) ) ) then - return false, ("ERROR: Failed to connect to Domino server %s:%d\n"):format(self.host, self.port) - end - return true - end, + --- Connects the socket to the Domino server + -- + -- @return status true on success, false on failure + -- @return err error message if status is false + connect = function( self ) + if( not( self.domsock:connect( self.host.ip, self.port.number, "tcp" ) ) ) then + return false, ("ERROR: Failed to connect to Domino server %s:%d\n"):format(self.host, self.port) + end + return true + end, - --- Disconnects from the Lotus Domino Server - -- - -- @return status true on success, false on failure - -- @return err error message if status is false - disconnect = function( self ) - return self.domsock:close() - end, + --- Disconnects from the Lotus Domino Server + -- + -- @return status true on success, false on failure + -- @return err error message if status is false + disconnect = function( self ) + return self.domsock:close() + end, - --- Attempt to check whether the user exists in Domino or not - -- - -- @param username string containing the user name to guess - -- @return status true on success false on failure - -- @return domino_id if it exists and status is true - -- err if status is false - isValidUser = function( self, username ) - local data = bin.pack("H", "00001e00000001000080000007320000700104020000fb2b2d00281f1e000000124c010000000000") - local status, id_data - local data_len, pos, total_len, pkt_type, valid_user + --- Attempt to check whether the user exists in Domino or not + -- + -- @param username string containing the user name to guess + -- @return status true on success false on failure + -- @return domino_id if it exists and status is true + -- err if status is false + isValidUser = function( self, username ) + local data = bin.pack("H", "00001e00000001000080000007320000700104020000fb2b2d00281f1e000000124c010000000000") + local status, id_data + local data_len, pos, total_len, pkt_type, valid_user - self.domsock:send( tostring(DominoPacket:new( data )) ) - data = DominoPacket:new():read( self.domsock ) + self.domsock:send( tostring(DominoPacket:new( data )) ) + data = DominoPacket:new():read( self.domsock ) - data = bin.pack("HCHAH", "0100320002004f000100000500000900", #username + 1, "000000000000000000000000000000000028245573657273290000", username, "00") - self.domsock:send( tostring(DominoPacket:new( data ) ) ) - status, id_data = DominoPacket:new():read( self.domsock ) + data = bin.pack("HCHAH", "0100320002004f000100000500000900", #username + 1, "000000000000000000000000000000000028245573657273290000", username, "00") + self.domsock:send( tostring(DominoPacket:new( data ) ) ) + status, id_data = DominoPacket:new():read( self.domsock ) - pos, pkt_type = bin.unpack("C", id_data, 3) - pos, valid_user = bin.unpack("C", id_data, 11) - pos, total_len = bin.unpack(" 0x7f then - ch = string.byte(".", 1) - end - io.write(string.format("%c", ch)) - end + -- Loop through the string again, this time the ascii + for char=1, 16, 1 do + local ch = string.byte(str, ((line - 1) * 16) + char) + if ch < 0x20 or ch > 0x7f then + ch = string.byte(".", 1) + end + io.write(string.format("%c", ch)) + end - io.write("\n") - end + io.write("\n") + end - -- Prints out the final, partial line - if (#str % 16 ~= 0) then - local line = math.floor((#str/16)) + 1 - io.write(string.format("%08x ", (line - 1) * 16)) + -- Prints out the final, partial line + if (#str % 16 ~= 0) then + local line = math.floor((#str/16)) + 1 + io.write(string.format("%08x ", (line - 1) * 16)) - for char=1, #str % 16, 1 do - local ch = string.byte(str, ((line - 1) * 16) + char) - io.write(string.format("%02x ", ch)) - end - io.write(string.rep(" ", 16 - (#str % 16))); - io.write(" ") + for char=1, #str % 16, 1 do + local ch = string.byte(str, ((line - 1) * 16) + char) + io.write(string.format("%02x ", ch)) + end + io.write(string.rep(" ", 16 - (#str % 16))); + io.write(" ") - for char=1, #str % 16, 1 do - local ch = string.byte(str, ((line - 1) * 16) + char) - if ch < 0x20 or ch > 0x7f then - ch = string.byte(".", 1) - end - io.write(string.format("%c", ch)) - end + for char=1, #str % 16, 1 do + local ch = string.byte(str, ((line - 1) * 16) + char) + if ch < 0x20 or ch > 0x7f then + ch = string.byte(".", 1) + end + io.write(string.format("%c", ch)) + end - io.write("\n") - end + io.write("\n") + end - -- Print out the length - io.write(string.format(" Length: %d [0x%x]\n", #str, #str)) + -- Print out the length + io.write(string.format(" Length: %d [0x%x]\n", #str, #str)) end ---Print out a stacktrace. The stacktrace will naturally include this function call. function print_stack() - local thread = coroutine.running() - local trace = debug.traceback(thread); - if trace ~= "stack traceback:" then - print(thread, "\n", trace, "\n"); - end + local thread = coroutine.running() + local trace = debug.traceback(thread); + if trace ~= "stack traceback:" then + print(thread, "\n", trace, "\n"); + end end diff --git a/nselib/ospf.lua b/nselib/ospf.lua index 0938fedce..b93f7a937 100644 --- a/nselib/ospf.lua +++ b/nselib/ospf.lua @@ -19,281 +19,281 @@ _ENV = stdnse.module("ospf", stdnse.seeall) -- The OSPF class. OSPF = { - -- Message Type constants - Message = { - HELLO = 1, - DB_DESCRIPTION = 2, - LS_UPDATE = 4, - }, + -- Message Type constants + Message = { + HELLO = 1, + DB_DESCRIPTION = 2, + LS_UPDATE = 4, + }, - LSUpdate = { + LSUpdate = { + + }, + + Header = { + size = 24, + new = function(self, type, area_id, router_id, auth_type, auth_data) + local o = { + ver = 2, + type = type, + length = 0, + router_id = router_id or 0, + area_id = area_id or 0, + chksum = 0, + auth_type = auth_type or 0, + auth_data = auth_data or {}, + } + setmetatable(o, self) + self.__index = self + return o + end, + + parse = function(data) + local header = OSPF.Header:new() + local pos + pos, header.ver, header.type, header.length = bin.unpack(">CCS", data) + assert( header.ver == 2, "Invalid OSPF version detected") + + pos, header.router_id, header.area_id, header.chksum, header.auth_type + = bin.unpack("ISS", data, pos) + + -- No authentication + if header.auth_type == 0x00 then + header.auth_data.password = nil + -- Clear text password + elseif header.auth_type == 0x01 then + pos, header.auth_data.password = bin.unpack(">A8", data, pos) + -- MD5 hash authentication + elseif header.auth_type == 0x02 then + local _ + _, header.auth_data.keyid = bin.unpack(">C", data, pos+2) + _, header.auth_data.length = bin.unpack(">C", data, pos+3) + _, header.auth_data.seq = bin.unpack(">C", data, pos+4) + _, header.auth_data.hash = bin.unpack(">H"..header.auth_data.length, data, header.length+1) + else + -- Shouldn't happen + stdnse.print_debug("Unknown authentication type " .. header.auth_type) + return nil + end + header.router_id = ipOps.fromdword(header.router_id) + return header + end, + + --- Sets the OSPF Area ID + -- @param areaid Area ID. + setAreaID = function(self, areaid) + self.area_id = (type(areaid) == "number") and areaid or ipOps.todword(areaid) + end, + + --- Sets the OSPF Router ID + -- @param router_id Router ID. + setRouterId = function(self, router_id) + self.router_id = router_id + end, + + --- Sets the OSPF Packet length + -- @param length Packet length. + setLength = function(self, length) + self.length = self.size + length + end, + + __tostring = function(self) + local hdr = bin.pack(">CCS", self.ver, self.type, self.length ) + hdr = hdr .. bin.pack(">IISS", ipOps.todword(self.router_id), self.area_id, self.chksum, self.auth_type) + if self.auth_type == 0x00 then + hdr = hdr .. bin.pack(">L", 0x00) + elseif self.auth_type == 0x01 then + hdr = hdr .. bin.pack(">A8", self.auth_data.password) + elseif self.auth_type == 0x02 then + hdr = hdr .. bin.pack(">A".. self.auth_data.length, self.auth_data.hash) + end + return hdr + end, + + }, + + Hello = { + new = function(self) + local o = { + header = OSPF.Header:new(OSPF.Message.HELLO), + options = 0x02, + prio = 0, + interval = 10, + router_dead_interval = 40, + neighbors = {}, + DR = "0.0.0.0", + BDR = "0.0.0.0", + } + setmetatable(o, self) + self.__index = self + return o + end, + + --- Adds a neighbor to the list of neighbors. + -- @param neighbor IP Address of the neighbor. + addNeighbor = function(self, neighbor) + table.insert(self.neighbors, neighbor) + end, + + --- Sets the OSPF netmask. + -- @param netmask Netmask in A.B.C.D + setNetmask = function(self, netmask) + if netmask then + self.netmask = netmask + end + end, + + --- Sets the OSPF designated Router. + -- @param router IP address of the designated router. + setDesignatedRouter = function(self, router) + if router then + self.DR = router + end + end, + + --- Sets the OSPF backup Router. + -- @param router IP Address of the backup router. + setBackupRouter = function(self, router) + if router then + self.BDR = router + end + end, + + __tostring = function(self) + self.neighbors = self.neighbors or {} + local function tostr() + local data = bin.pack(">ISCCIII", ipOps.todword(self.netmask), self.interval, self.options, self.prio, self.router_dead_interval, ipOps.todword(self.DR), ipOps.todword(self.BDR)) + for _, n in ipairs(self.neighbors) do + data = data .. bin.pack(">I", ipOps.todword(n)) + end + self.header:setLength(#data) + return tostring(self.header) .. data + end + local data = tostr() + self.header.chksum = packet.in_cksum(data:sub(1,12) .. data:sub(25)) + return tostr() + end, + + parse = function(data) + local hello = OSPF.Hello:new() + local pos = OSPF.Header.size + 1 + hello.header = OSPF.Header.parse(data) + assert( #data >= hello.header.length, "OSPF packet too short") + pos, hello.netmask, hello.interval, hello.options, hello.prio, + hello.router_dead_interval, hello.DR, + hello.BDR = bin.unpack("CCS", data) - assert( header.ver == 2, "Invalid OSPF version detected") + __tostring = function(self) + local function tostr() + local flags = 0 + if ( self.init ) then flags = flags + 4 end + if ( self.more ) then flags = flags + 2 end + if ( self.master) then flags= flags + 1 end - pos, header.router_id, header.area_id, header.chksum, header.auth_type - = bin.unpack("ISS", data, pos) + local data = bin.pack(">SCCI", self.mtu, self.options, flags, self.sequence) + self.header:setLength(#data) + return tostring(self.header) .. data + end + local data = tostr() + self.header.chksum = packet.in_cksum(data:sub(1,12) .. data:sub(25)) + return tostr() + end, - -- No authentication - if header.auth_type == 0x00 then - header.auth_data.password = nil - -- Clear text password - elseif header.auth_type == 0x01 then - pos, header.auth_data.password = bin.unpack(">A8", data, pos) - -- MD5 hash authentication - elseif header.auth_type == 0x02 then - local _ - _, header.auth_data.keyid = bin.unpack(">C", data, pos+2) - _, header.auth_data.length = bin.unpack(">C", data, pos+3) - _, header.auth_data.seq = bin.unpack(">C", data, pos+4) - _, header.auth_data.hash = bin.unpack(">H"..header.auth_data.length, data, header.length+1) - else - -- Shouldn't happen - stdnse.print_debug("Unknown authentication type " .. header.auth_type) - return nil - end - header.router_id = ipOps.fromdword(header.router_id) - return header - end, + parse = function(data) + local desc = OSPF.DBDescription:new() + local pos = OSPF.Header.size + 1 + desc.header = OSPF.Header.parse(data) + assert( #data == desc.header.length, "OSPF packet too short") - --- Sets the OSPF Area ID - -- @param areaid Area ID. - setAreaID = function(self, areaid) - self.area_id = (type(areaid) == "number") and areaid or ipOps.todword(areaid) - end, + local flags = 0 + pos, desc.mtu, desc.options, flags, desc.sequence = bin.unpack(">SCCI", data, pos) - --- Sets the OSPF Router ID - -- @param router_id Router ID. - setRouterId = function(self, router_id) - self.router_id = router_id - end, + desc.init = ( bit.band(flags, 4) == 4 ) + desc.more = ( bit.band(flags, 2) == 2 ) + desc.master = ( bit.band(flags, 1) == 1 ) - --- Sets the OSPF Packet length - -- @param length Packet length. - setLength = function(self, length) - self.length = self.size + length - end, + if ( desc.init or not(desc.more) ) then + return desc + end - __tostring = function(self) - local hdr = bin.pack(">CCS", self.ver, self.type, self.length ) - hdr = hdr .. bin.pack(">IISS", ipOps.todword(self.router_id), self.area_id, self.chksum, self.auth_type) - if self.auth_type == 0x00 then - hdr = hdr .. bin.pack(">L", 0x00) - elseif self.auth_type == 0x01 then - hdr = hdr .. bin.pack(">A8", self.auth_data.password) - elseif self.auth_type == 0x02 then - hdr = hdr .. bin.pack(">A".. self.auth_data.length, self.auth_data.hash) - end - return hdr - end, + return desc + end, - }, + }, - Hello = { - new = function(self) - local o = { - header = OSPF.Header:new(OSPF.Message.HELLO), - options = 0x02, - prio = 0, - interval = 10, - router_dead_interval = 40, - neighbors = {}, - DR = "0.0.0.0", - BDR = "0.0.0.0", - } - setmetatable(o, self) - self.__index = self - return o - end, + Response = { - --- Adds a neighbor to the list of neighbors. - -- @param neighbor IP Address of the neighbor. - addNeighbor = function(self, neighbor) - table.insert(self.neighbors, neighbor) - end, + parse = function(data) + local pos, ver, ospf_type = bin.unpack("CC", data) + if ( ospf_type == OSPF.Message.HELLO ) then + return OSPF.Hello.parse( data ) + elseif( ospf_type == OSPF.Message.DB_DESCRIPTION ) then + return OSPF.DBDescription.parse(data) + end + return + end, - --- Sets the OSPF netmask. - -- @param netmask Netmask in A.B.C.D - setNetmask = function(self, netmask) - if netmask then - self.netmask = netmask - end - end, - - --- Sets the OSPF designated Router. - -- @param router IP address of the designated router. - setDesignatedRouter = function(self, router) - if router then - self.DR = router - end - end, - - --- Sets the OSPF backup Router. - -- @param router IP Address of the backup router. - setBackupRouter = function(self, router) - if router then - self.BDR = router - end - end, - - __tostring = function(self) - self.neighbors = self.neighbors or {} - local function tostr() - local data = bin.pack(">ISCCIII", ipOps.todword(self.netmask), self.interval, self.options, self.prio, self.router_dead_interval, ipOps.todword(self.DR), ipOps.todword(self.BDR)) - for _, n in ipairs(self.neighbors) do - data = data .. bin.pack(">I", ipOps.todword(n)) - end - self.header:setLength(#data) - return tostring(self.header) .. data - end - local data = tostr() - self.header.chksum = packet.in_cksum(data:sub(1,12) .. data:sub(25)) - return tostr() - end, - - parse = function(data) - local hello = OSPF.Hello:new() - local pos = OSPF.Header.size + 1 - hello.header = OSPF.Header.parse(data) - assert( #data >= hello.header.length, "OSPF packet too short") - pos, hello.netmask, hello.interval, hello.options, hello.prio, - hello.router_dead_interval, hello.DR, - hello.BDR = bin.unpack("SCCI", self.mtu, self.options, flags, self.sequence) - self.header:setLength(#data) - return tostring(self.header) .. data - end - local data = tostr() - self.header.chksum = packet.in_cksum(data:sub(1,12) .. data:sub(25)) - return tostr() - end, - - parse = function(data) - local desc = OSPF.DBDescription:new() - local pos = OSPF.Header.size + 1 - desc.header = OSPF.Header.parse(data) - assert( #data == desc.header.length, "OSPF packet too short") - - local flags = 0 - pos, desc.mtu, desc.options, flags, desc.sequence = bin.unpack(">SCCI", data, pos) - - desc.init = ( bit.band(flags, 4) == 4 ) - desc.more = ( bit.band(flags, 2) == 2 ) - desc.master = ( bit.band(flags, 1) == 1 ) - - if ( desc.init or not(desc.more) ) then - return desc - end - - return desc - end, - - }, - - Response = { - - parse = function(data) - local pos, ver, ospf_type = bin.unpack("CC", data) - if ( ospf_type == OSPF.Message.HELLO ) then - return OSPF.Hello.parse( data ) - elseif( ospf_type == OSPF.Message.DB_DESCRIPTION ) then - return OSPF.DBDescription.parse(data) - end - return - end, - - } + } } return _ENV; diff --git a/nselib/pop3.lua b/nselib/pop3.lua index 982272641..e217a5a41 100644 --- a/nselib/pop3.lua +++ b/nselib/pop3.lua @@ -15,11 +15,11 @@ local HAVE_SSL, openssl = pcall(require,'openssl') err = { - none = 0, - userError = 1, - pwError = 2, - informationMissing = 3, - OpenSSLMissing = 4, + none = 0, + userError = 1, + pwError = 2, + informationMissing = 3, + OpenSSLMissing = 4, } --- @@ -27,7 +27,7 @@ err = { -- @param line First line returned from an POP3 request. -- @return The string "+OK" if found or nil otherwise. function stat(line) - return string.match(line, "+OK") + return string.match(line, "+OK") end @@ -40,16 +40,16 @@ end -- @return Status (true or false). -- @return Error code if status is false. function login_user(socket, user, pw) - socket:send("USER " .. user .. "\r\n") - local status, line = socket:receive_lines(1) - if not stat(line) then return false, err.user_error end - socket:send("PASS " .. pw .. "\r\n") + socket:send("USER " .. user .. "\r\n") + local status, line = socket:receive_lines(1) + if not stat(line) then return false, err.user_error end + socket:send("PASS " .. pw .. "\r\n") - status, line = socket:receive_lines(1) + status, line = socket:receive_lines(1) - if stat(line) then return true, err.none - else return false, err.pwError - end + if stat(line) then return true, err.none + else return false, err.pwError + end end @@ -62,16 +62,16 @@ end -- @return Error code if status is false. function login_sasl_plain(socket, user, pw) - local auth64 = base64.enc(user .. "\0" .. user .. "\0" .. pw) - socket:send("AUTH PLAIN " .. auth64 .. "\r\n") + local auth64 = base64.enc(user .. "\0" .. user .. "\0" .. pw) + socket:send("AUTH PLAIN " .. auth64 .. "\r\n") - local status, line = socket:receive_lines(1) + local status, line = socket:receive_lines(1) - if stat(line) then - return true, err.none - else - return false, err.pwError - end + if stat(line) then + return true, err.none + else + return false, err.pwError + end end --- @@ -83,34 +83,34 @@ end -- @return Error code if status is false. function login_sasl_login(socket, user, pw) - local user64 = base64.enc(user) + local user64 = base64.enc(user) - local pw64 = base64.enc(pw) + local pw64 = base64.enc(pw) - socket:send("AUTH LOGIN\r\n") + socket:send("AUTH LOGIN\r\n") - local status, line = socket:receive_lines(1) - if not base64.dec(string.sub(line, 3)) == "User Name:" then - return false, err.userError - end + local status, line = socket:receive_lines(1) + if not base64.dec(string.sub(line, 3)) == "User Name:" then + return false, err.userError + end - socket:send(user64) + socket:send(user64) - local status, line = socket:receive_lines(1) + local status, line = socket:receive_lines(1) - if not base64.dec(string.sub(line, 3)) == "Password:" then - return false, err.userError - end + if not base64.dec(string.sub(line, 3)) == "Password:" then + return false, err.userError + end - socket:send(pw64) + socket:send(pw64) - local status, line = socket:receive_lines(1) + local status, line = socket:receive_lines(1) - if stat(line) then - return true, err.none - else - return false, err.pwError - end + if stat(line) then + return true, err.none + else + return false, err.pwError + end end --- @@ -122,18 +122,18 @@ end -- @return Status (true or false). -- @return Error code if status is false. function login_apop(socket, user, pw, challenge) - if type(challenge) ~= "string" then return false, err.informationMissing end + if type(challenge) ~= "string" then return false, err.informationMissing end - local apStr = stdnse.tohex(openssl.md5(challenge .. pw)) - socket:send(("APOP %s %s\r\n"):format(user, apStr)) + local apStr = stdnse.tohex(openssl.md5(challenge .. pw)) + socket:send(("APOP %s %s\r\n"):format(user, apStr)) - local status, line = socket:receive_lines(1) + local status, line = socket:receive_lines(1) - if (stat(line)) then - return true, err.none - else - return false, err.pwError - end + if (stat(line)) then + return true, err.none + else + return false, err.pwError + end end --- @@ -146,49 +146,49 @@ end -- @return nil or String error message. function capabilities(host, port) - local socket, line, bopt, first_line = comm.tryssl(host, port, "" , {timeout=10000, recv_before=true}) - if not socket then - return nil, "Could Not Connect" - end - if not stat(first_line) then - return nil, "No Response" - end + local socket, line, bopt, first_line = comm.tryssl(host, port, "" , {timeout=10000, recv_before=true}) + if not socket then + return nil, "Could Not Connect" + end + if not stat(first_line) then + return nil, "No Response" + end - local capas = {} - if string.find(first_line, "<[%p%w]+>") then - capas.APOP = {} - end + local capas = {} + if string.find(first_line, "<[%p%w]+>") then + capas.APOP = {} + end - local status = socket:send("CAPA\r\n") - if( not(status) ) then - return nil, "Failed to send" - end + local status = socket:send("CAPA\r\n") + if( not(status) ) then + return nil, "Failed to send" + end - status, line = socket:receive_buf("%.", false) - if( not(status) ) then - return nil, "Failed to receive" - end - socket:close() + status, line = socket:receive_buf("%.", false) + if( not(status) ) then + return nil, "Failed to receive" + end + socket:close() - local lines = stdnse.strsplit("\r\n",line) - if not stat(table.remove(lines,1)) then - capas.capa = false - return capas - end + local lines = stdnse.strsplit("\r\n",line) + if not stat(table.remove(lines,1)) then + capas.capa = false + return capas + end - for _, line in ipairs(lines) do - if ( line and #line>0 ) then - local capability = line:sub(line:find("[%w-]+")) - line = line:sub(#capability + 2) - if ( line ~= "" ) then - capas[capability] = stdnse.strsplit(" ", line) - else - capas[capability] = {} - end - end - end + for _, line in ipairs(lines) do + if ( line and #line>0 ) then + local capability = line:sub(line:find("[%w-]+")) + line = line:sub(#capability + 2) + if ( line ~= "" ) then + capas[capability] = stdnse.strsplit(" ", line) + else + capas[capability] = {} + end + end + end - return capas + return capas end --- @@ -200,23 +200,23 @@ end -- @return Error code if status is false. function login_sasl_crammd5(socket, user, pw) - socket:send("AUTH CRAM-MD5\r\n") + socket:send("AUTH CRAM-MD5\r\n") - local status, line = socket:receive_lines(1) + local status, line = socket:receive_lines(1) - local challenge = base64.dec(string.sub(line, 3)) + local challenge = base64.dec(string.sub(line, 3)) - local digest = stdnse.tohex(openssl.hmac('md5', pw, challenge)) - local authStr = base64.enc(user .. " " .. digest) - socket:send(authStr .. "\r\n") + local digest = stdnse.tohex(openssl.hmac('md5', pw, challenge)) + local authStr = base64.enc(user .. " " .. digest) + socket:send(authStr .. "\r\n") - local status, line = socket:receive_lines(1) + local status, line = socket:receive_lines(1) - if stat(line) then - return true, err.none - else - return false, err.pwError - end + if stat(line) then + return true, err.none + else + return false, err.pwError + end end -- Overwrite functions requiring OpenSSL if we got no OpenSSL. diff --git a/nselib/proxy.lua b/nselib/proxy.lua index c937a822f..59714d283 100644 --- a/nselib/proxy.lua +++ b/nselib/proxy.lua @@ -211,7 +211,7 @@ function socksHandshake(socket, version, hostname) stdnse.print_debug("Socks4: Received \"request failed because client is not running identd\" from proxy server") elseif (request_status == 0x5d) then stdnse.print_debug("Socks4: Received \"request failed because client's identd could not confirm" .. - "\nthe user ID string in the request from proxy server") + "\nthe user ID string in the request from proxy server") end return false end @@ -234,24 +234,24 @@ function socksHandshake(socket, version, hostname) local z = try(socket:receive()) local request_status = string.byte(z, 2) if (request_status == 0x00) then - stdnse.print_debug("Socks5: Received \"Request Granted\" from proxy server\n") - return socket + stdnse.print_debug("Socks5: Received \"Request Granted\" from proxy server\n") + return socket elseif(request_status == 0x01) then - stdnse.print_debug("Socks5: Received \"General failure\" from proxy server") + stdnse.print_debug("Socks5: Received \"General failure\" from proxy server") elseif (request_status == 0x02) then - stdnse.print_debug("Socks5: Received \"Connection not allowed by ruleset\" from proxy server") + stdnse.print_debug("Socks5: Received \"Connection not allowed by ruleset\" from proxy server") elseif (request_status == 0x03) then - stdnse.print_debug("Socks5: Received \"Network unreachable\" from proxy server") + stdnse.print_debug("Socks5: Received \"Network unreachable\" from proxy server") elseif (request_status == 0x04) then - stdnse.print_debug("Socks5: Received \"Host unreachable\" from proxy server") + stdnse.print_debug("Socks5: Received \"Host unreachable\" from proxy server") elseif (request_status == 0x05) then - stdnse.print_debug("Socks5: Received \"Connection refused by destination host\" from proxy server") + stdnse.print_debug("Socks5: Received \"Connection refused by destination host\" from proxy server") elseif (request_status == 0x06) then - stdnse.print_debug("Socks5: Received \"TTL Expired\" from proxy server") + stdnse.print_debug("Socks5: Received \"TTL Expired\" from proxy server") elseif (request_status == 0x07) then - stdnse.print_debug("Socks5: Received \"command not supported / protocol error\" from proxy server") + stdnse.print_debug("Socks5: Received \"command not supported / protocol error\" from proxy server") elseif (request_status == 0x08) then - stdnse.print_debug("Socks5: Received \"Address type not supported\" from proxy server") + stdnse.print_debug("Socks5: Received \"Address type not supported\" from proxy server") end end return false diff --git a/nselib/rdp.lua b/nselib/rdp.lua index 2b093a607..5aafbde78 100644 --- a/nselib/rdp.lua +++ b/nselib/rdp.lua @@ -14,325 +14,325 @@ _ENV = stdnse.module("rdp", stdnse.seeall) Packet = { - TPKT = { + TPKT = { - new = function(self, data) - local o = { data = tostring(data), version = 3 } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, data) + local o = { data = tostring(data), version = 3 } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return bin.pack(">CCSA", - self.version, - self.reserved or 0, - (self.data and #self.data + 4 or 4), - self.data - ) - end, + __tostring = function(self) + return bin.pack(">CCSA", + self.version, + self.reserved or 0, + (self.data and #self.data + 4 or 4), + self.data + ) + end, - parse = function(data) - local tpkt = Packet.TPKT:new() - local pos + parse = function(data) + local tpkt = Packet.TPKT:new() + local pos - pos, tpkt.version, tpkt.reserved, tpkt.length = bin.unpack(">CCS", data) - pos, tpkt.data = bin.unpack("A" .. (#data - pos), data, pos) - return tpkt - end - }, + pos, tpkt.version, tpkt.reserved, tpkt.length = bin.unpack(">CCS", data) + pos, tpkt.data = bin.unpack("A" .. (#data - pos), data, pos) + return tpkt + end + }, - ITUT = { + ITUT = { - new = function(self, code, data) - local o = { data = tostring(data), code = code } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, code, data) + local o = { data = tostring(data), code = code } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local itut = Packet.ITUT:new() - local pos + parse = function(data) + local itut = Packet.ITUT:new() + local pos - pos, itut.length, itut.code = bin.unpack("CC", data) + pos, itut.length, itut.code = bin.unpack("CC", data) - if ( itut.code == 0xF0 ) then - pos, itut.eot = bin.unpack("C", data, pos) - elseif ( itut.code == 0xD0 ) then - pos, itut.dstref, itut.srcref, itut.class = bin.unpack(">SSC", data, pos) - end + if ( itut.code == 0xF0 ) then + pos, itut.eot = bin.unpack("C", data, pos) + elseif ( itut.code == 0xD0 ) then + pos, itut.dstref, itut.srcref, itut.class = bin.unpack(">SSC", data, pos) + end - pos, itut.data = bin.unpack("A" .. (#data - pos), data, pos) - return itut - end, + pos, itut.data = bin.unpack("A" .. (#data - pos), data, pos) + return itut + end, - __tostring = function(self) - local len = (self.code ~= 0xF0 and #self.data + 1 or 2) - local data = bin.pack("CC", - len, - self.code or 0 - ) + __tostring = function(self) + local len = (self.code ~= 0xF0 and #self.data + 1 or 2) + local data = bin.pack("CC", + len, + self.code or 0 + ) - if ( self.code == 0xF0 ) then - data = data .. bin.pack("C", 0x80) -- EOT - end + if ( self.code == 0xF0 ) then + data = data .. bin.pack("C", 0x80) -- EOT + end - return data .. self.data - end, + return data .. self.data + end, - }, + }, } Request = { - ConnectionRequest = { + ConnectionRequest = { - new = function(self, proto) - local o = { proto = proto } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, proto) + local o = { proto = proto } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - local cookie = "mstshash=nmap" - local itpkt_len = 21 + #cookie - local itut_len = 16 + #cookie + __tostring = function(self) + local cookie = "mstshash=nmap" + local itpkt_len = 21 + #cookie + local itut_len = 16 + #cookie - local data = bin.pack(">SSCA", - 0x0000, -- dst reference - 0x0000, -- src reference - 0x00, -- class and options - ("Cookie: %s\r\n"):format(cookie)) + local data = bin.pack(">SSCA", + 0x0000, -- dst reference + 0x0000, -- src reference + 0x00, -- class and options + ("Cookie: %s\r\n"):format(cookie)) - if ( self.proto ) then - data = data .. bin.pack("SSSSAA", self.type, DUMMY, #self.username, #self.password, self.username, self.password) - end, + __tostring = function(self) + local DUMMY = 0 + return bin.pack(">SSSSAA", self.type, DUMMY, #self.username, #self.password, self.username, self.password) + end, - }, + }, - NULL = { + NULL = { - new = function(self) - local o = { - type = 0, - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { + type = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - local DUMMY = 0 - return bin.pack(">SSSS", self.type, DUMMY, 0, 0) - end, + __tostring = function(self) + local DUMMY = 0 + return bin.pack(">SSSS", self.type, DUMMY, 0, 0) + end, - } + } - }, + }, - -- The common request and response header - Header = { - size = 8, - new = function(self, type, value, length) - local o = { - version = 0, - type = type, - value= value or 0, - length = length or 0 - } - setmetatable(o, self) - self.__index = self - return o - end, + -- The common request and response header + Header = { + size = 8, + new = function(self, type, value, length) + local o = { + version = 0, + type = type, + value= value or 0, + length = length or 0 + } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local header = RPCAP.Header:new() - local pos - pos, header.version, header.type, header.value, header.length = bin.unpack(">CCSI", data) - return header - end, + parse = function(data) + local header = RPCAP.Header:new() + local pos + pos, header.version, header.type, header.value, header.length = bin.unpack(">CCSI", data) + return header + end, - __tostring = function(self) - return bin.pack(">CCSI", self.version, self.type, self.value, self.length) - end, + __tostring = function(self) + return bin.pack(">CCSI", self.version, self.type, self.value, self.length) + end, - }, + }, - -- The implemented request types are kept here - Request = { + -- The implemented request types are kept here + Request = { - Authentication = { + Authentication = { - new = function(self, data) - local o = { - header = RPCAP.Header:new(RPCAP.MessageType.AUTH_REQUEST, nil, #data), - data = data, - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, data) + local o = { + header = RPCAP.Header:new(RPCAP.MessageType.AUTH_REQUEST, nil, #data), + data = data, + } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return tostring(self.header) .. tostring(self.data) - end, + __tostring = function(self) + return tostring(self.header) .. tostring(self.data) + end, - }, + }, - FindAllInterfaces = { + FindAllInterfaces = { - new = function(self) - local o = { - header = RPCAP.Header:new(RPCAP.MessageType.FIND_ALL_INTERFACES) - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self) + local o = { + header = RPCAP.Header:new(RPCAP.MessageType.FIND_ALL_INTERFACES) + } + setmetatable(o, self) + self.__index = self + return o + end, - __tostring = function(self) - return tostring(self.header) - end, + __tostring = function(self) + return tostring(self.header) + end, - } + } - }, + }, - -- Parsers for responses are kept here - Response = { + -- Parsers for responses are kept here + Response = { - Authentication = { - new = function(self) - local o = { } - setmetatable(o, self) - self.__index = self - return o - end, + Authentication = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local resp = RPCAP.Response.Authentication:new() - local pos = RPCAP.Header.size + 1 - resp.header = RPCAP.Header.parse(data) - return resp - end - }, + parse = function(data) + local resp = RPCAP.Response.Authentication:new() + local pos = RPCAP.Header.size + 1 + resp.header = RPCAP.Header.parse(data) + return resp + end + }, - Error = { - new = function(self) - local o = { } - setmetatable(o, self) - self.__index = self - return o - end, + Error = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) - local err = RPCAP.Response.Error:new() - local pos = RPCAP.Header.size + 1 - err.header = RPCAP.Header.parse(data) - pos, err.error = bin.unpack("A" .. err.header.length, data, pos) - return err - end + parse = function(data) + local err = RPCAP.Response.Error:new() + local pos = RPCAP.Header.size + 1 + err.header = RPCAP.Header.parse(data) + pos, err.error = bin.unpack("A" .. err.header.length, data, pos) + return err + end - }, + }, - FindAllInterfaces = { - new = function(self) - local o = { } - setmetatable(o, self) - self.__index = self - return o - end, + FindAllInterfaces = { + new = function(self) + local o = { } + setmetatable(o, self) + self.__index = self + return o + end, - parse = function(data) + parse = function(data) - -- Each address is made up of 4 128 byte fields, this function - -- parses these fields and return the response, if it - -- understands it. Otherwise it simply increases the pos by the - -- correct offset, to get us to the next field. - local function parseField(data, pos) - local offset = pos - local family, port - pos, family, port = bin.unpack(">SS", data, pos) + -- Each address is made up of 4 128 byte fields, this function + -- parses these fields and return the response, if it + -- understands it. Otherwise it simply increases the pos by the + -- correct offset, to get us to the next field. + local function parseField(data, pos) + local offset = pos + local family, port + pos, family, port = bin.unpack(">SS", data, pos) - if ( family == 0x0017 ) then - -- not sure why... - pos = pos + 4 + if ( family == 0x0017 ) then + -- not sure why... + pos = pos + 4 - local ipv6 - pos, ipv6 = bin.unpack("B16", data, pos) - return offset + 128, ipOps.bin_to_ip(ipv6) - elseif ( family == 0x0002 ) then - local ipv4 - pos, ipv4 = bin.unpack("B4", data, pos) - return offset + 128, ipOps.bin_to_ip(ipv4) - end + local ipv6 + pos, ipv6 = bin.unpack("B16", data, pos) + return offset + 128, ipOps.bin_to_ip(ipv6) + elseif ( family == 0x0002 ) then + local ipv4 + pos, ipv4 = bin.unpack("B4", data, pos) + return offset + 128, ipOps.bin_to_ip(ipv4) + end - return offset + 128, nil - end + return offset + 128, nil + end - -- Parses one of X addresses returned for an interface - local function parseAddress(data, pos) - local fields = {"ip", "netmask", "bcast", "p2p"} - local addr = {} + -- Parses one of X addresses returned for an interface + local function parseAddress(data, pos) + local fields = {"ip", "netmask", "bcast", "p2p"} + local addr = {} - for _, f in ipairs(fields) do - pos, addr[f] = parseField(data, pos) - end + for _, f in ipairs(fields) do + pos, addr[f] = parseField(data, pos) + end - return pos, addr - end + return pos, addr + end - local resp = RPCAP.Response.FindAllInterfaces:new() - local pos = RPCAP.Header.size + 1 - resp.header = RPCAP.Header.parse(data) - resp.ifaces = {} + local resp = RPCAP.Response.FindAllInterfaces:new() + local pos = RPCAP.Header.size + 1 + resp.header = RPCAP.Header.parse(data) + resp.ifaces = {} - for i=1, resp.header.value do - local name_len, desc_len, iface_flags, addr_count, dummy - pos, name_len, desc_len, iface_flags, addr_count, dummy = bin.unpack(">SSISS", data, pos) + for i=1, resp.header.value do + local name_len, desc_len, iface_flags, addr_count, dummy + pos, name_len, desc_len, iface_flags, addr_count, dummy = bin.unpack(">SSISS", data, pos) - local name, desc - pos, name, desc = bin.unpack("A" .. name_len .. "A" .. desc_len, data, pos) + local name, desc + pos, name, desc = bin.unpack("A" .. name_len .. "A" .. desc_len, data, pos) - local addrs = {} - for j=1, addr_count do - local addr - pos, addr = parseAddress(data, pos) - local cidr - if ( addr.netmask ) then - local bits = ipOps.ip_to_bin(addr.netmask) - local ones = bits:match("^(1*)") - cidr = #ones - table.insert(addrs, ("%s/%d"):format(addr.ip,cidr)) - else - table.insert(addrs, addr.ip) - end - end - table.insert(resp.ifaces, { name = name, desc = desc, addrs = addrs }) - end - return resp - end, - } + local addrs = {} + for j=1, addr_count do + local addr + pos, addr = parseAddress(data, pos) + local cidr + if ( addr.netmask ) then + local bits = ipOps.ip_to_bin(addr.netmask) + local ones = bits:match("^(1*)") + cidr = #ones + table.insert(addrs, ("%s/%d"):format(addr.ip,cidr)) + else + table.insert(addrs, addr.ip) + end + end + table.insert(resp.ifaces, { name = name, desc = desc, addrs = addrs }) + end + return resp + end, + } - } + } } -- Maps packet types to classes RPCAP.TypeToClass = { - [1] = RPCAP.Response.Error, - [130] = RPCAP.Response.FindAllInterfaces, - [136] = RPCAP.Response.Authentication, + [1] = RPCAP.Response.Error, + [130] = RPCAP.Response.FindAllInterfaces, + [136] = RPCAP.Response.Authentication, } -- The communication class Comm = { - -- Creates a new instance of the Comm class - -- @param host table - -- @param port table - -- @return o instance of Comm - new = function(self, host, port) - local o = { host = host, port = port, socket = nmap.new_socket() } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the Comm class + -- @param host table + -- @param port table + -- @return o instance of Comm + new = function(self, host, port) + local o = { host = host, port = port, socket = nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects the socket to the server - connect = function(self) - return self.socket:connect(self.host, self.port) - end, + -- Connects the socket to the server + connect = function(self) + return self.socket:connect(self.host, self.port) + end, - -- Sends an instance of the request class to the server - -- @param req class instance - -- @return status true on success, false on failure - -- @return err string containing error message if status is false - send = function(self, req) - return self.socket:send(req) - end, + -- Sends an instance of the request class to the server + -- @param req class instance + -- @return status true on success, false on failure + -- @return err string containing error message if status is false + send = function(self, req) + return self.socket:send(req) + end, - -- receives a packet and attempts to parse it if it has a supported parser - -- in RPCAP.TypeToClass - -- @return status true on success, false on failure - -- @return resp instance of a Response class or - -- err string containing the error message - recv = function(self) - local status, hdr_data = self.socket:receive_buf(match.numbytes(RPCAP.Header.size), true) - if ( not(status) ) then - return status, hdr_data - end + -- receives a packet and attempts to parse it if it has a supported parser + -- in RPCAP.TypeToClass + -- @return status true on success, false on failure + -- @return resp instance of a Response class or + -- err string containing the error message + recv = function(self) + local status, hdr_data = self.socket:receive_buf(match.numbytes(RPCAP.Header.size), true) + if ( not(status) ) then + return status, hdr_data + end - local header = RPCAP.Header.parse(hdr_data) - if ( not(header) ) then - return false, "rpcap: Failed to parse header" - end + local header = RPCAP.Header.parse(hdr_data) + if ( not(header) ) then + return false, "rpcap: Failed to parse header" + end - local status, data = self.socket:receive_buf(match.numbytes(header.length), true) - if ( not(status) ) then - return false, "rpcap: Failed to read packet data" - end + local status, data = self.socket:receive_buf(match.numbytes(header.length), true) + if ( not(status) ) then + return false, "rpcap: Failed to read packet data" + end - if ( RPCAP.TypeToClass[header.type] ) then - local resp = RPCAP.TypeToClass[header.type].parse(hdr_data .. data) - if ( resp ) then - return true, resp - end - end + if ( RPCAP.TypeToClass[header.type] ) then + local resp = RPCAP.TypeToClass[header.type].parse(hdr_data .. data) + if ( resp ) then + return true, resp + end + end - return false, "Failed to receive response from server" - end, + return false, "Failed to receive response from server" + end, - -- Sends and request and receives the response - -- @param req the instance of the Request class to send - -- @return status true on success, false on failure - -- @return resp instance of a Response class or - -- err string containing the error message - exch = function(self, req) - local status, data = self:send(tostring(req)) - if ( not(status) ) then - return status, data - end - return self:recv() - end, + -- Sends and request and receives the response + -- @param req the instance of the Request class to send + -- @return status true on success, false on failure + -- @return resp instance of a Response class or + -- err string containing the error message + exch = function(self, req) + local status, data = self:send(tostring(req)) + if ( not(status) ) then + return status, data + end + return self:recv() + end, - -- closes the socket - close = function(self) - return self.socket:close() - end, + -- closes the socket + close = function(self) + return self.socket:close() + end, } Helper = { - -- Creates a new instance of the Helper class - -- @param host table - -- @param port table - -- @return o instance of Helper - new = function(self, host, port) - local o = { - host = host, - port = port, - comm = Comm:new(host, port) - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the Helper class + -- @param host table + -- @param port table + -- @return o instance of Helper + new = function(self, host, port) + local o = { + host = host, + port = port, + comm = Comm:new(host, port) + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects to the server - connect = function(self) - return self.comm:connect(self.host, self.port) - end, + -- Connects to the server + connect = function(self) + return self.comm:connect(self.host, self.port) + end, - -- Authenticates to the service, in case no username or password is given - -- NULL authentication is assumed. - -- @param username [optional] - -- @param password [optional] - -- @return status true on success, false on failure - -- @return err string containing error mesage on failure - login = function(self, username, password) - local auth + -- Authenticates to the service, in case no username or password is given + -- NULL authentication is assumed. + -- @param username [optional] + -- @param password [optional] + -- @return status true on success, false on failure + -- @return err string containing error mesage on failure + login = function(self, username, password) + local auth - if ( username and password ) then - auth = RPCAP.Authentication.PWD:new(username, password) - else - auth = RPCAP.Authentication.NULL:new() - end + if ( username and password ) then + auth = RPCAP.Authentication.PWD:new(username, password) + else + auth = RPCAP.Authentication.NULL:new() + end - local req = RPCAP.Request.Authentication:new(tostring(auth)) - local status, resp = self.comm:exch(req) + local req = RPCAP.Request.Authentication:new(tostring(auth)) + local status, resp = self.comm:exch(req) - if ( not(status) ) then - return false, resp - end + if ( not(status) ) then + return false, resp + end - if ( status and resp.error ) then - return false, resp.error - end - return true - end, + if ( status and resp.error ) then + return false, resp.error + end + return true + end, - -- Requests a list of all interfaces - -- @return table containing interfaces and addresses - findAllInterfaces = function(self) - local req = RPCAP.Request.FindAllInterfaces:new() - local status, resp = self.comm:exch(req) + -- Requests a list of all interfaces + -- @return table containing interfaces and addresses + findAllInterfaces = function(self) + local req = RPCAP.Request.FindAllInterfaces:new() + local status, resp = self.comm:exch(req) - if ( not(status) ) then - return false, resp - end + if ( not(status) ) then + return false, resp + end - local results = {} - for _, iface in ipairs(resp.ifaces) do - local entry = {} - entry.name = iface.name - table.insert(entry, iface.desc) - table.insert(entry, { name = "Addresses", iface.addrs }) - table.insert(results, entry) - end - return true, results - end, + local results = {} + for _, iface in ipairs(resp.ifaces) do + local entry = {} + entry.name = iface.name + table.insert(entry, iface.desc) + table.insert(entry, { name = "Addresses", iface.addrs }) + table.insert(results, entry) + end + return true, results + end, - -- Closes the connection to the server - close = function(self) - return self.comm:close() - end, + -- Closes the connection to the server + close = function(self) + return self.comm:close() + end, } return _ENV; diff --git a/nselib/rsync.lua b/nselib/rsync.lua index c3141893c..4ef87a93d 100644 --- a/nselib/rsync.lua +++ b/nselib/rsync.lua @@ -16,159 +16,159 @@ _ENV = stdnse.module("rsync", stdnse.seeall) -- The Helper class serves as the main interface for script writers Helper = { - -- Creates a new instance of the Helper class - -- @param host table as received by the action function - -- @param port table as received by the action function - -- @param options table containing any additional options - -- @return o instance of Helper - new = function(self, host, port, options) - local o = { host = host, port = port, options = options or {} } - assert(o.options.module, "No rsync module was specified, aborting ...") - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the Helper class + -- @param host table as received by the action function + -- @param port table as received by the action function + -- @param options table containing any additional options + -- @return o instance of Helper + new = function(self, host, port, options) + local o = { host = host, port = port, options = options or {} } + assert(o.options.module, "No rsync module was specified, aborting ...") + setmetatable(o, self) + self.__index = self + return o + end, - -- Handles send and receive of control messages - -- @param data string containing the command to send - -- @return status true on succes, false on failure - -- @return data containing the response from the server - -- err string, if status is false - ctrl_exch = function(self, data) - local status, err = self.socket:send(data.."\n") - if ( not(status) ) then - return false, err - end - local status, data = self.socket:receive_buf("\n", false) - if( not(status) ) then - return false, err - end - return true, data - end, + -- Handles send and receive of control messages + -- @param data string containing the command to send + -- @return status true on succes, false on failure + -- @return data containing the response from the server + -- err string, if status is false + ctrl_exch = function(self, data) + local status, err = self.socket:send(data.."\n") + if ( not(status) ) then + return false, err + end + local status, data = self.socket:receive_buf("\n", false) + if( not(status) ) then + return false, err + end + return true, data + end, - -- Connects to the rsync server - -- @return status, true on success, false on failure - -- @return err string containing an error message if status is false - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(self.options.timeout or 5000) - local status, err = self.socket:connect(self.host, self.port) - if ( not(status) ) then - return false, err - end + -- Connects to the rsync server + -- @return status, true on success, false on failure + -- @return err string containing an error message if status is false + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(self.options.timeout or 5000) + local status, err = self.socket:connect(self.host, self.port) + if ( not(status) ) then + return false, err + end - local data - status, data = self:ctrl_exch("@RSYNCD: 29") - if ( not(status) ) then - return false, data - end - if ( not(data:match("^@RSYNCD: [%.%d]+$")) ) then - return false, "Protocol error" - end - return true - end, + local data + status, data = self:ctrl_exch("@RSYNCD: 29") + if ( not(status) ) then + return false, data + end + if ( not(data:match("^@RSYNCD: [%.%d]+$")) ) then + return false, "Protocol error" + end + return true + end, - -- Authenticates against the rsync module. If no username is given, assume - -- no authentication is required. - -- @param username [optional] string containing the username - -- @param password [optional] string containing the password - login = function(self, username, password) - password = password or "" - local status, data = self:ctrl_exch(self.options.module) - if (not(status)) then - return false, data - end + -- Authenticates against the rsync module. If no username is given, assume + -- no authentication is required. + -- @param username [optional] string containing the username + -- @param password [optional] string containing the password + login = function(self, username, password) + password = password or "" + local status, data = self:ctrl_exch(self.options.module) + if (not(status)) then + return false, data + end - local chall - if ( data:match("@RSYNCD: OK") ) then - return true, "No authentication was required" - else - chall = data:match("^@RSYNCD: AUTHREQD (.*)$") - if ( not(chall) and data:match("^@ERROR: Unknown module") ) then - return false, data:match("^@ERROR: (.*)$") - elseif ( not(chall) ) then - return false, "Failed to retrieve challenge" - end - end + local chall + if ( data:match("@RSYNCD: OK") ) then + return true, "No authentication was required" + else + chall = data:match("^@RSYNCD: AUTHREQD (.*)$") + if ( not(chall) and data:match("^@ERROR: Unknown module") ) then + return false, data:match("^@ERROR: (.*)$") + elseif ( not(chall) ) then + return false, "Failed to retrieve challenge" + end + end - if ( chall and not(username) ) then - return false, "Authentication required" - end + if ( chall and not(username) ) then + return false, "Authentication required" + end - local md4 = openssl.md4("\0\0\0\0" .. password .. chall) - local resp = base64.enc(md4):sub(1,-3) - status, data = self:ctrl_exch(username .. " " .. resp) - if (not(status)) then - return false, data - end + local md4 = openssl.md4("\0\0\0\0" .. password .. chall) + local resp = base64.enc(md4):sub(1,-3) + status, data = self:ctrl_exch(username .. " " .. resp) + if (not(status)) then + return false, data + end - if ( data == "@RSYNCD: OK" ) then - return true, "Authentication successfull" - end - return false, "Authentication failed" - end, + if ( data == "@RSYNCD: OK" ) then + return true, "Authentication successfull" + end + return false, "Authentication failed" + end, - -- Lists accessible modules from the rsync server - -- @return status true on success, false on failure - -- @return modules table containing a list of modules - listModules = function(self) - local status, data = self.socket:send("\n") - if (not(status)) then - return false, data - end + -- Lists accessible modules from the rsync server + -- @return status true on success, false on failure + -- @return modules table containing a list of modules + listModules = function(self) + local status, data = self.socket:send("\n") + if (not(status)) then + return false, data + end - local modules = {} - while(true) do - status, data = self.socket:receive_buf("\n", false) - if (not(status)) then - return false, data - end - if ( data == "@RSYNCD: EXIT" ) then - break - else - table.insert(modules, data) - end - end - return true, modules - end, + local modules = {} + while(true) do + status, data = self.socket:receive_buf("\n", false) + if (not(status)) then + return false, data + end + if ( data == "@RSYNCD: EXIT" ) then + break + else + table.insert(modules, data) + end + end + return true, modules + end, - -- Lists the files available for the directory/module - -- TODO: Add support for parsing results, seemed straight forward at - -- first, but wasn't. - listFiles = function(self) - -- list recursively and enable MD4 checksums - local data = ("--server\n--sender\n-rc\n.\n%s\n\n"):format(self.options.module) - local status, data = self.socket:send(data) - if ( not(status) ) then - return false, data - end - status, data = self.socket:receive_bytes(4) - if ( not(status) ) then - return false, data - end + -- Lists the files available for the directory/module + -- TODO: Add support for parsing results, seemed straight forward at + -- first, but wasn't. + listFiles = function(self) + -- list recursively and enable MD4 checksums + local data = ("--server\n--sender\n-rc\n.\n%s\n\n"):format(self.options.module) + local status, data = self.socket:send(data) + if ( not(status) ) then + return false, data + end + status, data = self.socket:receive_bytes(4) + if ( not(status) ) then + return false, data + end - status, data = self.socket:send("\0\0\0\0") - if ( not(status) ) then - return false, data - end + status, data = self.socket:send("\0\0\0\0") + if ( not(status) ) then + return false, data + end - status, data = self.socket:receive_buf(match.numbytes(4), false) - if ( not(status) ) then - return false, data - end + status, data = self.socket:receive_buf(match.numbytes(4), false) + if ( not(status) ) then + return false, data + end - local pos, len = bin.unpack(" 0 ) then - req = req .. stdnse.strjoin("\r\n", self.headers) .. "\r\n" - end + local req = stdnse.strjoin("\r\n", { + ("%s %s RTSP/1.0"):format(self.method, self.url), + ("CSeq: %d"):format(self.cseq) + } ) .. "\r\n" + if ( #self.headers > 0 ) then + req = req .. stdnse.strjoin("\r\n", self.headers) .. "\r\n" + end - return req .. "\r\n" - end, + return req .. "\r\n" + end, } -- The RTSP response instance Response = { - --- Creates a new Response instance - -- @param data string containing the unparsed data - new = function(self, data) - assert(data, "No data was supplied") - local o = { - raw = data, - status = tonumber(data:match("^RTSP%/1%.0 (%d*) ")) - } + --- Creates a new Response instance + -- @param data string containing the unparsed data + new = function(self, data) + assert(data, "No data was supplied") + local o = { + raw = data, + status = tonumber(data:match("^RTSP%/1%.0 (%d*) ")) + } - -- Split the response into a temporary array - local tmp = stdnse.strsplit("\r\n", data) - if ( not(tmp) ) then return nil end + -- Split the response into a temporary array + local tmp = stdnse.strsplit("\r\n", data) + if ( not(tmp) ) then return nil end - -- we should have atleas one entry - if ( #tmp > 1 ) then - o.headers = {} - for i=2, #tmp do - -- if we have an empty line, this should be the end of headers - if ( #tmp[i] == 0 ) then break end - local key, val = tmp[i]:match("^(.-): (.*)$") - -- create a key per header name - o.headers[key] = val - end - end + -- we should have atleas one entry + if ( #tmp > 1 ) then + o.headers = {} + for i=2, #tmp do + -- if we have an empty line, this should be the end of headers + if ( #tmp[i] == 0 ) then break end + local key, val = tmp[i]:match("^(.-): (.*)$") + -- create a key per header name + o.headers[key] = val + end + end - setmetatable(o, self) - self.__index = self - return o - end, + setmetatable(o, self) + self.__index = self + return o + end, } @@ -128,163 +128,163 @@ Response = { -- RTSP Client class Client = { - -- Creates a new Client instance - -- @param host table as received by the action method - -- @param port table as received by the action method - -- @return o instance of Client - new = function(self, host, port) - local o = { - host = host, - port = port, - cseq = 0, - headers = { }, - retries = 3, - timeout = 10 * 1000, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Client instance + -- @param host table as received by the action method + -- @param port table as received by the action method + -- @return o instance of Client + new = function(self, host, port) + local o = { + host = host, + port = port, + cseq = 0, + headers = { }, + retries = 3, + timeout = 10 * 1000, + } + setmetatable(o, self) + self.__index = self + return o + end, - --- Sets the number of retries for socket reads - -- @param retries number containing the number of retries - setRetries = function(self, retries) self.retries = retries end, + --- Sets the number of retries for socket reads + -- @param retries number containing the number of retries + setRetries = function(self, retries) self.retries = retries end, - --- Sets the socket connection timeout in ms - -- @param timeout number containing the timeout in ms - setTimeout = function(self, timeout) self.timeout = timeout end, + --- Sets the socket connection timeout in ms + -- @param timeout number containing the timeout in ms + setTimeout = function(self, timeout) self.timeout = timeout end, - --- Adds a RTSP header to the request - -- @param header string containing the header name - -- @param value string containing the header value - addHeader = function(self, header, value) - table.insert(self.headers, { ("%s: %s"):format(header,value) } ) - end, + --- Adds a RTSP header to the request + -- @param header string containing the header name + -- @param value string containing the header value + addHeader = function(self, header, value) + table.insert(self.headers, { ("%s: %s"):format(header,value) } ) + end, - --- Connects to the RTSP server - -- @return status true on success, false on failure - -- @return err string containing the error message on failure - connect = function(self) - self.socket = nmap.new_socket() - self.socket:set_timeout(self.timeout) - local status = self.socket:connect(self.host, self.port) - if ( not(status) ) then - stdnse.print_debug(2, "Failed to connect to the server: %s", self.host.ip) - return false, ("Failed to connect to the server: %s"):format(self.host.ip) - end - return true - end, + --- Connects to the RTSP server + -- @return status true on success, false on failure + -- @return err string containing the error message on failure + connect = function(self) + self.socket = nmap.new_socket() + self.socket:set_timeout(self.timeout) + local status = self.socket:connect(self.host, self.port) + if ( not(status) ) then + stdnse.print_debug(2, "Failed to connect to the server: %s", self.host.ip) + return false, ("Failed to connect to the server: %s"):format(self.host.ip) + end + return true + end, - --- Sends a DESCRIBE request to the server and receives the response - -- @param url string containing the RTSP URL - -- @return status true on success, false on failure - -- @return response Response instance on success - -- err string containing the error message on failure - describe = function(self, url) - local req = Request:new(url, self.headers) - req:setMethod("DESCRIBE") - return self:exch(req) - end, + --- Sends a DESCRIBE request to the server and receives the response + -- @param url string containing the RTSP URL + -- @return status true on success, false on failure + -- @return response Response instance on success + -- err string containing the error message on failure + describe = function(self, url) + local req = Request:new(url, self.headers) + req:setMethod("DESCRIBE") + return self:exch(req) + end, - options = function(self, url) - local req = Request:new(url, self.headers) - req:setMethod("OPTIONS") - return self:exch(req) - end, + options = function(self, url) + local req = Request:new(url, self.headers) + req:setMethod("OPTIONS") + return self:exch(req) + end, - --- Sends a request to the server and receives the response and attempts - -- to retry if either send or receive fails. - -- @param request instance of Request - -- @return status true on success, false on failure - -- @return response Response instance on success - -- err string containing the error message on failure - exch = function(self, req) - local retries = self.retries - local status, data - self.cseq = self.cseq + 1 - req:setCSeq( self.cseq ) + --- Sends a request to the server and receives the response and attempts + -- to retry if either send or receive fails. + -- @param request instance of Request + -- @return status true on success, false on failure + -- @return response Response instance on success + -- err string containing the error message on failure + exch = function(self, req) + local retries = self.retries + local status, data + self.cseq = self.cseq + 1 + req:setCSeq( self.cseq ) - repeat - local err - status, err = self.socket:send( tostring(req) ) - -- check if send was successfull, in case it wasn't AND - -- this is our last retry, ABORT - if ( not(status) and 0 == retries - 1 ) then - stdnse.print_debug(2, "Failed to send request to server (%s)", err) - return false, ("Failed to send request to server (%s)"):format(err) - -- if send was successfull, attempt to receive the response - elseif ( status ) then - status, data = self.socket:receive() - -- if we got the response allright, break out of retry loop - if ( status ) then break end - end - -- if either send or receive fails, re-connect the socket - if ( not(status) ) then - self:close() - local status, err = self:connect() - -- if re-connect fails, BAIL out of here - if ( not(status) ) then - stdnse.print_debug(2, "Failed to reconnect socket to server (%s)", err) - return false, ("Failed to reconnect socket to server (%s)"):format(err) - end - end - retries = retries - 1 - until( status or retries == 0 ) + repeat + local err + status, err = self.socket:send( tostring(req) ) + -- check if send was successfull, in case it wasn't AND + -- this is our last retry, ABORT + if ( not(status) and 0 == retries - 1 ) then + stdnse.print_debug(2, "Failed to send request to server (%s)", err) + return false, ("Failed to send request to server (%s)"):format(err) + -- if send was successfull, attempt to receive the response + elseif ( status ) then + status, data = self.socket:receive() + -- if we got the response allright, break out of retry loop + if ( status ) then break end + end + -- if either send or receive fails, re-connect the socket + if ( not(status) ) then + self:close() + local status, err = self:connect() + -- if re-connect fails, BAIL out of here + if ( not(status) ) then + stdnse.print_debug(2, "Failed to reconnect socket to server (%s)", err) + return false, ("Failed to reconnect socket to server (%s)"):format(err) + end + end + retries = retries - 1 + until( status or retries == 0 ) - if( not(status) ) then - stdnse.print_debug(2, "Failed to receive response from server (%s)", data) - return false, ("Failed to receive response from server (%s)"):format(data) - end + if( not(status) ) then + stdnse.print_debug(2, "Failed to receive response from server (%s)", data) + return false, ("Failed to receive response from server (%s)"):format(data) + end - return true, Response:new(data) - end, + return true, Response:new(data) + end, - --- Closes the RTSP socket with the server - close = function(self) - return self.socket:close() - end, + --- Closes the RTSP socket with the server + close = function(self) + return self.socket:close() + end, } -- The Helper class is the main script interface Helper = { - -- Creates a new Helper instance - -- @param host table as received by the action method - -- @param port table as received by the action method - -- @return o instance of Client - new = function(self, host, port) - local o = { host = host, port = port, client = Client:new(host, port) } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Helper instance + -- @param host table as received by the action method + -- @param port table as received by the action method + -- @return o instance of Client + new = function(self, host, port) + local o = { host = host, port = port, client = Client:new(host, port) } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects to the RTSP server - -- @return status true on success, false on failure - -- @return err string containing the error message on failure - connect = function(self) - return self.client:connect() - end, + -- Connects to the RTSP server + -- @return status true on success, false on failure + -- @return err string containing the error message on failure + connect = function(self) + return self.client:connect() + end, - -- Closes the RTSP socket with the server - close = function(self) - return self.client:close() - end, + -- Closes the RTSP socket with the server + close = function(self) + return self.client:close() + end, - -- Sends a DESCRIBE request to the server and receives the response - -- - -- @param url string containing the RTSP URL - -- @return status true on success, false on failure - -- @return response string containing the unparsed RTSP response on success - -- err string containing the error message on failure - describe = function(self, url) - return self.client:describe(url) - end, + -- Sends a DESCRIBE request to the server and receives the response + -- + -- @param url string containing the RTSP URL + -- @return status true on success, false on failure + -- @return response string containing the unparsed RTSP response on success + -- err string containing the error message on failure + describe = function(self, url) + return self.client:describe(url) + end, - options = function(self, url) - return self.client:options(url) - end, + options = function(self, url) + return self.client:options(url) + end, } diff --git a/nselib/sasl.lua b/nselib/sasl.lua index e81783a02..7c94aafb5 100644 --- a/nselib/sasl.lua +++ b/nselib/sasl.lua @@ -8,8 +8,8 @@ -- necessary arguments (@see DigestMD5.new). -- It can be called throught the SASL helper or directly like this: -- --- local dmd5 = DigestMD5:new(chall, user, pass, "AUTHENTICATE", nil, "imap") --- local digest = dmd5:calcDigest() +-- local dmd5 = DigestMD5:new(chall, user, pass, "AUTHENTICATE", nil, "imap") +-- local digest = dmd5:calcDigest() -- -- -- The NTLM class contains all code necessary to calculate a @@ -17,8 +17,8 @@ -- arguments (@see NTLM.new). It can be called through the SASL helper or -- directly like this: -- --- local ntlm = NTLM:new(chall, user, pass) --- local response = ntlm:calcResponse() +-- local ntlm = NTLM:new(chall, user, pass) +-- local response = ntlm:calcResponse() -- -- -- The Helper class contains the high level methodes: @@ -37,8 +37,8 @@ -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html -- Version 0.2 --- Created 07/17/2011 - v0.1 - Created by Djalal Harouini --- Revised 07/18/2011 - v0.2 - Added NTLM, DIGEST-MD5 classes +-- Created 07/17/2011 - v0.1 - Created by Djalal Harouini +-- Revised 07/18/2011 - v0.2 - Added NTLM, DIGEST-MD5 classes local bin = require "bin" @@ -50,8 +50,8 @@ _ENV = stdnse.module("sasl", stdnse.seeall) local HAVE_SSL, openssl = pcall(require, 'openssl') if ( not(HAVE_SSL) ) then - stdnse.print_debug(1, - "sasl.lua: OpenSSL not present, SASL support limited.") + stdnse.print_debug(1, + "sasl.lua: OpenSSL not present, SASL support limited.") end local MECHANISMS = { } @@ -59,22 +59,22 @@ if HAVE_SSL then -- Calculates a DIGEST MD5 response DigestMD5 = { - --- Instantiates DigestMD5 - -- - -- @param chall string containing the base64 decoded challenge - -- @return a new instance of DigestMD5 + --- Instantiates DigestMD5 + -- + -- @param chall string containing the base64 decoded challenge + -- @return a new instance of DigestMD5 new = function(self, chall, username, password, method, uri, service, realm) local o = { nc = 0, - chall = chall, - challnvs = {}, - username = username, - password = password, - method = method, - uri = uri, - service = service, - realm = realm } - setmetatable(o, self) - self.__index = self + chall = chall, + challnvs = {}, + username = username, + password = password, + method = method, + uri = uri, + service = service, + realm = realm } + setmetatable(o, self) + self.__index = self o:parseChallenge() return o end, @@ -121,8 +121,8 @@ if HAVE_SSL then local A1 = stdnse.tohex(openssl.md5(A1_part1 .. ":" .. self.challnvs.nonce .. ':' .. cnonce)) local A2 = stdnse.tohex(openssl.md5(("%s:%s"):format(self.method, uri))) local digest = stdnse.tohex(openssl.md5(A1 .. ":" .. self.challnvs.nonce .. ":" .. - ("%08d"):format(self.nc) .. ":" .. cnonce .. ":" .. - qop .. ":" .. A2)) + ("%08d"):format(self.nc) .. ":" .. cnonce .. ":" .. + qop .. ":" .. A2)) local b1 if not self.challnvs.algorithm or self.challnvs.algorithm == "MD5" then @@ -136,7 +136,7 @@ if HAVE_SSL then local digest_http if not qop_not_specified then digest_http = stdnse.tohex(openssl.md5(b1 .. ":" .. self.challnvs.nonce .. ":" .. - ("%08d"):format(self.nc) .. ":" .. cnonce .. ":" .. qop .. ":" .. A2)) + ("%08d"):format(self.nc) .. ":" .. cnonce .. ":" .. qop .. ":" .. A2)) else digest_http = stdnse.tohex(openssl.md5(b1 .. ":" .. self.challnvs.nonce .. ":" .. A2)) end @@ -170,113 +170,113 @@ if HAVE_SSL then } - -- The NTLM class handling NTLM challenge response authentication - NTLM = { + -- The NTLM class handling NTLM challenge response authentication + NTLM = { - --- Creates a new instance of the NTLM class - -- - -- @param chall string containing the challenge received from the server - -- @param username string containing the username - -- @param password string containing the password - -- @return new instance of NTML - new = function(self, chall, username, password) - local o = { nc = 0, - chall = chall, - username = username, - password = password} - setmetatable(o, self) - self.__index = self - o:parseChallenge() - return o - end, + --- Creates a new instance of the NTLM class + -- + -- @param chall string containing the challenge received from the server + -- @param username string containing the username + -- @param password string containing the password + -- @return new instance of NTML + new = function(self, chall, username, password) + local o = { nc = 0, + chall = chall, + username = username, + password = password} + setmetatable(o, self) + self.__index = self + o:parseChallenge() + return o + end, - --- Converst str to "unicode" (adds null bytes for every other byte) - -- @param str containing string to convert - -- @return unicode string containing the unicoded str - to_unicode = function(str) - local unicode = "" - for i = 1, #str, 1 do - unicode = unicode .. bin.pack("CRAM-MD5 mechanism. -- @@ -287,8 +287,8 @@ if HAVE_SSL then -- compiled without OpenSSL. function cram_md5_enc(username, password, challenge) local encode = stdnse.tohex(openssl.hmac('md5', - password, - challenge)) + password, + challenge)) return username.." "..encode end @@ -303,16 +303,16 @@ if HAVE_SSL then -- @return string The encoded string on success, or nil if Nmap was -- compiled without OpenSSL. function digest_md5_enc(username, password, challenge, service, uri) - return DigestMD5:new(challenge, - username, - password, - "AUTHENTICATE", - uri, - service):calcDigest() + return DigestMD5:new(challenge, + username, + password, + "AUTHENTICATE", + uri, + service):calcDigest() end function ntlm_enc(username, password, challenge) - return NTLM:new(challenge, username, password):calcResponse() + return NTLM:new(challenge, username, password):calcResponse() end else diff --git a/nselib/shortport.lua b/nselib/shortport.lua index a7b74ca1b..f6a23f7f7 100644 --- a/nselib/shortport.lua +++ b/nselib/shortport.lua @@ -16,12 +16,12 @@ _ENV = stdnse.module("shortport", stdnse.seeall) -- @param value The value to check for. -- @return True if t contains value, false otherwise. local function includes(t, value) - for _, elem in ipairs(t) do - if elem == value then - return true - end - end - return false + for _, elem in ipairs(t) do + if elem == value then + return true + end + end + return false end --- Check if the port and it's protocol are in the exclude directive. @@ -31,8 +31,8 @@ end -- @return True if the port and protocol are -- in the exclude directive. port_is_excluded = function(port, proto) - proto = proto or "tcp" - return nmap.port_is_excluded(port, proto) + proto = proto or "tcp" + return nmap.port_is_excluded(port, proto) end --- Return a portrule that returns true when given an open port matching a @@ -45,24 +45,24 @@ end -- @return Function for the portrule. -- @usage portrule = shortport.portnumber({80, 443}) portnumber = function(ports, protos, states) - protos = protos or "tcp" - states = states or {"open", "open|filtered"} + protos = protos or "tcp" + states = states or {"open", "open|filtered"} - if type(ports) ~= "table" then - ports = {ports} - end - if type(protos) ~= "table" then - protos = {protos} - end - if type(states) ~= "table" then - states = {states} - end + if type(ports) ~= "table" then + ports = {ports} + end + if type(protos) ~= "table" then + protos = {protos} + end + if type(states) ~= "table" then + states = {states} + end - return function(host, port) - return includes(ports, port.number) - and includes(protos, port.protocol) - and includes(states, port.state) - end + return function(host, port) + return includes(ports, port.number) + and includes(protos, port.protocol) + and includes(states, port.state) + end end --- Return a portrule that returns true when given an open port with a @@ -82,24 +82,24 @@ end -- @return Function for the portrule. -- @usage portrule = shortport.service("ftp") service = function(services, protos, states) - protos = protos or "tcp" - states = states or {"open", "open|filtered"} + protos = protos or "tcp" + states = states or {"open", "open|filtered"} - if type(services) ~= "table" then - services = {services} - end - if type(protos) ~= "table" then - protos = {protos} - end - if type(states) ~= "table" then - states = {states} - end + if type(services) ~= "table" then + services = {services} + end + if type(protos) ~= "table" then + protos = {protos} + end + if type(states) ~= "table" then + states = {states} + end - return function(host, port) - return includes(services, port.service) - and includes(protos, port.protocol) - and includes(states, port.state) - end + return function(host, port) + return includes(services, port.service) + and includes(protos, port.protocol) + and includes(states, port.state) + end end --- Return a portrule that returns true when given an open port matching @@ -119,11 +119,11 @@ end -- {"open", "open|filtered"}. -- @return Function for the portrule. port_or_service = function(ports, services, protos, states) - return function(host, port) - local port_checker = portnumber(ports, protos, states) - local service_checker = service(services, protos, states) - return port_checker(host, port) or service_checker(host, port) - end + return function(host, port) + local port_checker = portnumber(ports, protos, states) + local service_checker = service(services, protos, states) + return port_checker(host, port) or service_checker(host, port) + end end --- Return a portrule that returns true when given an open port matching @@ -144,24 +144,24 @@ end -- {"open", "open|filtered"}. -- @return Function for the portrule. version_port_or_service = function(ports, services, protos, states) - return function(host, port) - local p_s_check = port_or_service(ports, services, protos, states) - return p_s_check(host, port) - and not(port_is_excluded(port.number, port.protocol)) - end + return function(host, port) + local p_s_check = port_or_service(ports, services, protos, states) + return p_s_check(host, port) + and not(port_is_excluded(port.number, port.protocol)) + end end --[[ - Apache Tomcat HTTP server default ports: 8180 and 8000 - Litespeed webserver default ports: 8088 and 7080 +Apache Tomcat HTTP server default ports: 8180 and 8000 +Litespeed webserver default ports: 8088 and 7080 --]] LIKELY_HTTP_PORTS = { - 80, 443, 631, 7080, 8080, 8088, 5800, 3872, 8180, 8000 + 80, 443, 631, 7080, 8080, 8088, 5800, 3872, 8180, 8000 } LIKELY_HTTP_SERVICES = { - "http", "https", "ipp", "http-alt", "vnc-http", "oem-agent", "soap", - "http-proxy", + "http", "https", "ipp", "http-alt", "vnc-http", "oem-agent", "soap", + "http-proxy", } --- @@ -179,12 +179,12 @@ LIKELY_HTTP_SERVICES = { http = port_or_service(LIKELY_HTTP_PORTS, LIKELY_HTTP_SERVICES) local LIKELY_SSL_PORTS = { - 443, 465, 587, 636, 989, 990, 992, 993, 994, 995, 5061, 6679, 6697, 8443, - 9001, + 443, 465, 587, 636, 989, 990, 992, 993, 994, 995, 5061, 6679, 6697, 8443, + 9001, } local LIKELY_SSL_SERVICES = { - "ftps", "ftps-data", "https", "https-alt", "imaps", "ircs", - "ldapssl", "pop3s", "sip-tls", "smtps", "telnets", "tor-orport", + "ftps", "ftps-data", "https", "https-alt", "imaps", "ircs", + "ldapssl", "pop3s", "sip-tls", "smtps", "telnets", "tor-orport", } --- @@ -197,8 +197,8 @@ local LIKELY_SSL_SERVICES = { -- @usage -- portrule = shortport.ssl function ssl(host, port) - return port.version.service_tunnel == "ssl" or - port_or_service(LIKELY_SSL_PORTS, LIKELY_SSL_SERVICES, {"tcp", "sctp"})(host, port) + return port.version.service_tunnel == "ssl" or + port_or_service(LIKELY_SSL_PORTS, LIKELY_SSL_SERVICES, {"tcp", "sctp"})(host, port) end return _ENV; diff --git a/nselib/snmp.lua b/nselib/snmp.lua index 1798c0b81..6a69aa9a4 100644 --- a/nselib/snmp.lua +++ b/nselib/snmp.lua @@ -20,37 +20,37 @@ local tagEncoder = {} -- Override the boolean encoder tagEncoder['boolean'] = function(self, val) - return bin.pack('H', '05 00') + return bin.pack('H', '05 00') end -- Complex tag encoders tagEncoder['table'] = function(self, val) - if val._snmp == '06' then -- OID - local oidStr = string.char(val[1]*40 + val[2]) - for i = 3, #val do - oidStr = oidStr .. self.encode_oid_component(val[i]) - end - return bin.pack("HAA", '06', self.encodeLength(#oidStr), oidStr) + if val._snmp == '06' then -- OID + local oidStr = string.char(val[1]*40 + val[2]) + for i = 3, #val do + oidStr = oidStr .. self.encode_oid_component(val[i]) + end + return bin.pack("HAA", '06', self.encodeLength(#oidStr), oidStr) - elseif (val._snmp == '40') then -- ipAddress - return bin.pack("HC4", '40 04', table.unpack(val)) + elseif (val._snmp == '40') then -- ipAddress + return bin.pack("HC4", '40 04', table.unpack(val)) -- counter or gauge or timeticks or opaque - elseif (val._snmp == '41' or val._snmp == '42' or val._snmp == '43' or val._snmp == '44') then - local val = self:encodeInt(val[1]) - return bin.pack("HAA", val._snmp, self.encodeLength(#val), val) - end + elseif (val._snmp == '41' or val._snmp == '42' or val._snmp == '43' or val._snmp == '44') then + local val = self:encodeInt(val[1]) + return bin.pack("HAA", val._snmp, self.encodeLength(#val), val) + end - local encVal = "" - for _, v in ipairs(val) do - encVal = encVal .. self:encode(v) -- todo: buffer? - end + local encVal = "" + for _, v in ipairs(val) do + encVal = encVal .. self:encode(v) -- todo: buffer? + end - local tableType = bin.pack("H", "30") - if (val["_snmp"]) then - tableType = bin.pack("H", val["_snmp"]) - end - return bin.pack('AAA', tableType, self.encodeLength(#encVal), encVal) + local tableType = bin.pack("H", "30") + if (val["_snmp"]) then + tableType = bin.pack("H", val["_snmp"]) + end + return bin.pack('AAA', tableType, self.encodeLength(#encVal), encVal) end --- @@ -59,18 +59,18 @@ end -- @param val Value to be encoded. -- @return Encoded value. function encode(val) - local vtype = type(val) - local encoder = asn1.ASN1Encoder:new() - encoder:registerTagEncoders( tagEncoder ) + local vtype = type(val) + local encoder = asn1.ASN1Encoder:new() + encoder:registerTagEncoders( tagEncoder ) - local encVal = encoder:encode(val) + local encVal = encoder:encode(val) - if encVal then - return encVal - end + if encVal then + return encVal + end - return '' + return '' end -- SNMP ASN.1 Decoders @@ -91,10 +91,10 @@ tagDecoder["A2"] = function( self, encStr, elen, pos ) end tagDecoder["40"] = function( self, encStr, elen, pos ) - local ip = {} - pos, ip[1], ip[2], ip[3], ip[4] = bin.unpack("C4", encStr, pos) - ip._snmp = '40' - return pos, ip + local ip = {} + pos, ip[1], ip[2], ip[3], ip[4] = bin.unpack("C4", encStr, pos) + ip._snmp = '40' + return pos, ip end --- @@ -105,35 +105,35 @@ end -- @return The position after decoding -- @return The decoded value(s). function decode(encStr, pos) - local decoder = asn1.ASN1Decoder:new() + local decoder = asn1.ASN1Decoder:new() - if ( #tagDecoder == 0 ) then - decoder:registerBaseDecoders() - -- Application specific tags - -- tagDecoder["40"] = decoder.decoder["06"] -- IP Address; same as OID - tagDecoder["41"] = decoder.decoder["02"] -- Counter; same as Integer - tagDecoder["42"] = decoder.decoder["02"] -- Gauge - tagDecoder["43"] = decoder.decoder["02"] -- TimeTicks - tagDecoder["44"] = decoder.decoder["04"] -- Opaque; same as Octet String - tagDecoder["45"] = decoder.decoder["06"] -- NsapAddress - tagDecoder["46"] = decoder.decoder["02"] -- Counter64 - tagDecoder["47"] = decoder.decoder["02"] -- UInteger32 + if ( #tagDecoder == 0 ) then + decoder:registerBaseDecoders() + -- Application specific tags + -- tagDecoder["40"] = decoder.decoder["06"] -- IP Address; same as OID + tagDecoder["41"] = decoder.decoder["02"] -- Counter; same as Integer + tagDecoder["42"] = decoder.decoder["02"] -- Gauge + tagDecoder["43"] = decoder.decoder["02"] -- TimeTicks + tagDecoder["44"] = decoder.decoder["04"] -- Opaque; same as Octet String + tagDecoder["45"] = decoder.decoder["06"] -- NsapAddress + tagDecoder["46"] = decoder.decoder["02"] -- Counter64 + tagDecoder["47"] = decoder.decoder["02"] -- UInteger32 - -- Context specific tags - tagDecoder["A0"] = decoder.decoder["30"] -- GetRequest-PDU - tagDecoder["A1"] = decoder.decoder["30"] -- GetNextRequest-PDU - --tagDecoder["A2"] = decoder.decoder["30"] -- Response-PDU - tagDecoder["A3"] = decoder.decoder["30"] -- SetRequest-PDU - tagDecoder["A4"] = decoder.decoder["30"] -- Trap-PDU - tagDecoder["A5"] = decoder.decoder["30"] -- GetBulkRequest-PDU - tagDecoder["A6"] = decoder.decoder["30"] -- InformRequest-PDU (not implemented here yet) - tagDecoder["A7"] = decoder.decoder["30"] -- SNMPv2-Trap-PDU (not implemented here yet) - end + -- Context specific tags + tagDecoder["A0"] = decoder.decoder["30"] -- GetRequest-PDU + tagDecoder["A1"] = decoder.decoder["30"] -- GetNextRequest-PDU + --tagDecoder["A2"] = decoder.decoder["30"] -- Response-PDU + tagDecoder["A3"] = decoder.decoder["30"] -- SetRequest-PDU + tagDecoder["A4"] = decoder.decoder["30"] -- Trap-PDU + tagDecoder["A5"] = decoder.decoder["30"] -- GetBulkRequest-PDU + tagDecoder["A6"] = decoder.decoder["30"] -- InformRequest-PDU (not implemented here yet) + tagDecoder["A7"] = decoder.decoder["30"] -- SNMPv2-Trap-PDU (not implemented here yet) + end - decoder:registerTagDecoders( tagDecoder ) + decoder:registerTagDecoders( tagDecoder ) - return decoder:decode( encStr, pos ) + return decoder:decode( encStr, pos ) end --- @@ -143,10 +143,10 @@ end -- @param pos Current position in the string. -- @return The decoded value(s). function dec(encStr, pos) - local result - local _ - _, result = decode(encStr, pos) - return result + local result + local _ + _, result = decode(encStr, pos) + return result end --- @@ -156,17 +156,17 @@ end -- @param commStr community string, if not already supplied in registry or as -- the snmpcommunity script argument. function buildPacket(PDU, version, commStr) - local comm = nmap.registry.args.snmpcommunity - if (not comm) then comm = nmap.registry.snmpcommunity end - if (not comm) then comm = commStr end - if (not comm) then comm = "public" end + local comm = nmap.registry.args.snmpcommunity + if (not comm) then comm = nmap.registry.snmpcommunity end + if (not comm) then comm = commStr end + if (not comm) then comm = "public" end - if (not version) then version = 0 end - local packet = {} - packet[1] = version - packet[2] = comm - packet[3] = PDU - return packet + if (not version) then version = 0 end + local packet = {} + packet[1] = version + packet[2] = comm + packet[3] = PDU + return packet end @@ -179,29 +179,29 @@ end -- @param ... Object identifiers to be queried. -- @return Table representing PDU. function buildGetRequest(options, ...) - if not options then options = {} end + if not options then options = {} end - if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end - if not options.err then options.err = 0 end - if not options.errIdx then options.errIdx = 0 end + if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end + if not options.err then options.err = 0 end + if not options.errIdx then options.errIdx = 0 end - local req = {} - req._snmp = 'A0' - req[1] = options.reqId - req[2] = options.err - req[3] = options.errIdx + local req = {} + req._snmp = 'A0' + req[1] = options.reqId + req[2] = options.err + req[3] = options.errIdx - local payload = {} - for i=1, select('#', ...) do - payload[i] = {} - payload[i][1] = select(i, ...) - if type(payload[i][1]) == "string" then - payload[i][1] = str2oid(payload[i][1]) - end - payload[i][2] = false - end - req[4] = payload - return req + local payload = {} + for i=1, select('#', ...) do + payload[i] = {} + payload[i][1] = select(i, ...) + if type(payload[i][1]) == "string" then + payload[i][1] = str2oid(payload[i][1]) + end + payload[i][2] = false + end + req[4] = payload + return req end @@ -214,29 +214,29 @@ end -- @param ... Object identifiers to be queried. -- @return Table representing PDU. function buildGetNextRequest(options, ...) - if not options then options = {} end + if not options then options = {} end - if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end - if not options.err then options.err = 0 end - if not options.errIdx then options.errIdx = 0 end + if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end + if not options.err then options.err = 0 end + if not options.errIdx then options.errIdx = 0 end - local req = {} - req._snmp = 'A1' - req[1] = options.reqId - req[2] = options.err - req[3] = options.errIdx + local req = {} + req._snmp = 'A1' + req[1] = options.reqId + req[2] = options.err + req[3] = options.errIdx - local payload = {} - for i=1, select('#', ...) do - payload[i] = {} - payload[i][1] = select(i, ...) - if type(payload[i][1]) == "string" then - payload[i][1] = str2oid(payload[i][1]) - end - payload[i][2] = false - end - req[4] = payload - return req + local payload = {} + for i=1, select('#', ...) do + payload[i] = {} + payload[i][1] = select(i, ...) + if type(payload[i][1]) == "string" then + payload[i][1] = str2oid(payload[i][1]) + end + payload[i][2] = false + end + req[4] = payload + return req end --- @@ -252,60 +252,60 @@ end -- table instead of OID/value pair. -- @return Table representing PDU. function buildSetRequest(options, oid, value) - if not options then options = {} end + if not options then options = {} end - if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end - if not options.err then options.err = 0 end - if not options.errIdx then options.errIdx = 0 end + if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end + if not options.err then options.err = 0 end + if not options.errIdx then options.errIdx = 0 end - local req = {} - req._snmp = 'A3' - req[1] = options.reqId - req[2] = options.err - req[3] = options.errIdx + local req = {} + req._snmp = 'A3' + req[1] = options.reqId + req[2] = options.err + req[3] = options.errIdx - if (type(value) == "table") then - req[4] = value - else - local payload = {} - if (type(oid) == "string") then - payload[1] = str2oid(oid) - else - payload[1] = oid - end - payload[2] = value - req[4] = {} - req[4][1] = payload - end - return req + if (type(value) == "table") then + req[4] = value + else + local payload = {} + if (type(oid) == "string") then + payload[1] = str2oid(oid) + else + payload[1] = oid + end + payload[2] = value + req[4] = {} + req[4][1] = payload + end + return req end --- -- Create an SNMP Trap PDU. -- @return Table representing PDU function buildTrap(enterpriseOid, agentIp, genTrap, specTrap, timeStamp) - local req = {} - req._snmp = 'A4' - if (type(enterpriseOid) == "string") then - req[1] = str2oid(enterpriseOid) - else - req[1] = enterpriseOid - end - req[2] = {} - req[2]._snmp = '40' - for n in string.gmatch(agentIp, "%d+") do - table.insert(req[2], tonumber(n)) - end - req[3] = genTrap - req[4] = specTrap + local req = {} + req._snmp = 'A4' + if (type(enterpriseOid) == "string") then + req[1] = str2oid(enterpriseOid) + else + req[1] = enterpriseOid + end + req[2] = {} + req[2]._snmp = '40' + for n in string.gmatch(agentIp, "%d+") do + table.insert(req[2], tonumber(n)) + end + req[3] = genTrap + req[4] = specTrap - req[5] = {} - req[5]._snmp = '43' - req[5][1] = timeStamp + req[5] = {} + req[5]._snmp = '43' + req[5][1] = timeStamp - req[6] = {} + req[6] = {} - return req + return req end --- @@ -320,34 +320,34 @@ end -- @param value If given a table, use the table instead of OID/value pair. -- @return Table representing PDU. function buildGetResponse(options, oid, value) - if not options then options = {} end + if not options then options = {} end - -- if really a response, should use reqId of request! - if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end - if not options.err then options.err = 0 end - if not options.errIdx then options.errIdx = 0 end + -- if really a response, should use reqId of request! + if not options.reqId then options.reqId = math.fmod(nmap.clock_ms(), 65000) end + if not options.err then options.err = 0 end + if not options.errIdx then options.errIdx = 0 end - local resp = {} - resp._snmp = 'A2' - resp[1] = options.reqId - resp[2] = options.err - resp[3] = options.errIdx + local resp = {} + resp._snmp = 'A2' + resp[1] = options.reqId + resp[2] = options.err + resp[3] = options.errIdx - if (type(value) == "table") then - resp[4] = value - else + if (type(value) == "table") then + resp[4] = value + else - local payload = {} - if (type(oid) == "string") then - payload[1] = str2oid(oid) - else - payload[1] = oid - end - payload[2] = value - resp[4] = {} - resp[4][1] = payload - end - return resp + local payload = {} + if (type(oid) == "string") then + payload[1] = str2oid(oid) + else + payload[1] = oid + end + payload[2] = value + resp[4] = {} + resp[4][1] = payload + end + return resp end --- @@ -356,12 +356,12 @@ end -- "1.3.6.1.2.1.1.1.0". -- @return Table representing OID. function str2oid(oidStr) - local oid = {} - for n in string.gmatch(oidStr, "%d+") do - table.insert(oid, tonumber(n)) - end - oid._snmp = '06' - return oid + local oid = {} + for n in string.gmatch(oidStr, "%d+") do + table.insert(oid, tonumber(n)) + end + oid._snmp = '06' + return oid end --- @@ -369,8 +369,8 @@ end -- @param oid Object identifier table. -- @return OID string. function oid2str(oid) - if (type(oid) ~= "table") then return 'invalid oid' end - return table.concat(oid, '.') + if (type(oid) ~= "table") then return 'invalid oid' end + return table.concat(oid, '.') end --- @@ -378,8 +378,8 @@ end -- @param ip IP table. -- @return IP string. function ip2str(ip) - if (type(ip) ~= "table") then return 'invalid ip' end - return table.concat(ip, '.') + if (type(ip) ~= "table") then return 'invalid ip' end + return table.concat(ip, '.') end @@ -388,12 +388,12 @@ end -- @param ipStr IP as string. -- @return Table representing IP. function str2ip(ipStr) - local ip = {} - for n in string.gmatch(ipStr, "%d+") do - table.insert(ip, tonumber(n)) - end - ip._snmp = '40' - return ip + local ip = {} + for n in string.gmatch(ipStr, "%d+") do + table.insert(ip, tonumber(n)) + end + ip._snmp = '40' + return ip end @@ -402,44 +402,44 @@ end -- @param resp SNMP Response (will be decoded if necessary). -- @return Table with all decoded responses and their OIDs. function fetchResponseValues(resp) - if (type(resp) == "string") then - local _ - _, resp = decode(resp) - end + if (type(resp) == "string") then + local _ + _, resp = decode(resp) + end - if (type(resp) ~= "table") then - return {} - end + if (type(resp) ~= "table") then + return {} + end - local varBind - if (resp._snmp and resp._snmp == 'A2') then - varBind = resp[4] - elseif (resp[3]._snmp and resp[3]._snmp == 'A2') then - varBind = resp[3][4] - end + local varBind + if (resp._snmp and resp._snmp == 'A2') then + varBind = resp[4] + elseif (resp[3]._snmp and resp[3]._snmp == 'A2') then + varBind = resp[3][4] + end - if (varBind and type(varBind) == "table") then - local result = {} - for k, v in ipairs(varBind) do - local val = v[2] - if (type(v[2]) == "table") then - if (v[2]._snmp == '40') then - val = v[2][1] .. '.' .. v[2][2] .. '.' .. v[2][3] .. '.' .. v[2][4] - elseif (v[2]._snmp == '41') then - val = v[2][1] - elseif (v[2]._snmp == '42') then - val = v[2][1] - elseif (v[2]._snmp == '43') then - val = v[2][1] - elseif (v[2]._snmp == '44') then - val = v[2][1] - end - end - table.insert(result, {val, oid2str(v[1]), v[1]}) + if (varBind and type(varBind) == "table") then + local result = {} + for k, v in ipairs(varBind) do + local val = v[2] + if (type(v[2]) == "table") then + if (v[2]._snmp == '40') then + val = v[2][1] .. '.' .. v[2][2] .. '.' .. v[2][3] .. '.' .. v[2][4] + elseif (v[2]._snmp == '41') then + val = v[2][1] + elseif (v[2]._snmp == '42') then + val = v[2][1] + elseif (v[2]._snmp == '43') then + val = v[2][1] + elseif (v[2]._snmp == '44') then + val = v[2][1] + end end - return result - end - return {} + table.insert(result, {val, oid2str(v[1]), v[1]}) + end + return result + end + return {} end @@ -448,13 +448,13 @@ end -- @param response SNMP Response (will be decoded if necessary). -- @return First decoded value of the response. function fetchFirst(response) - local result = fetchResponseValues(response) + local result = fetchResponseValues(response) - if type(result) == "table" and result[1] and result[1][1] then - return result[1][1] + if type(result) == "table" and result[1] and result[1][1] then + return result[1][1] else - return nil - end + return nil + end end @@ -466,50 +466,50 @@ end -- @return table containing oid and value function snmpWalk( socket, base_oid ) - local snmp_table = {} - local oid = base_oid - local status, err, payload + local snmp_table = {} + local oid = base_oid + local status, err, payload - while ( true ) do + while ( true ) do - local value, response, snmpdata, options, item = nil, nil, nil, {}, {} - payload = encode( buildPacket( buildGetNextRequest(options, oid) ) ) + local value, response, snmpdata, options, item = nil, nil, nil, {}, {} + payload = encode( buildPacket( buildGetNextRequest(options, oid) ) ) - status, err = socket:send(payload) - if ( not( status ) ) then - stdnse.print_debug("snmp.snmpWalk: Send failed") - return false, err - end + status, err = socket:send(payload) + if ( not( status ) ) then + stdnse.print_debug("snmp.snmpWalk: Send failed") + return false, err + end - status, response = socket:receive_bytes(1) - if ( not( status ) ) then - -- Unless we don't have a usefull error message, don't report it - if ( response ~= "ERROR" ) then - stdnse.print_debug("snmp.snmpWalk: Received no answer (%s)", response) - return false, response - end - return false, nil - end + status, response = socket:receive_bytes(1) + if ( not( status ) ) then + -- Unless we don't have a usefull error message, don't report it + if ( response ~= "ERROR" ) then + stdnse.print_debug("snmp.snmpWalk: Received no answer (%s)", response) + return false, response + end + return false, nil + end - snmpdata = fetchResponseValues( response ) + snmpdata = fetchResponseValues( response ) - value = snmpdata[1][1] - oid = snmpdata[1][2] + value = snmpdata[1][1] + oid = snmpdata[1][2] - if not oid:match( base_oid ) or base_oid == oid then - break - end + if not oid:match( base_oid ) or base_oid == oid then + break + end - item.oid = oid - item.value = value + item.oid = oid + item.value = value - table.insert( snmp_table, item ) + table.insert( snmp_table, item ) - end + end - snmp_table.baseoid = base_oid + snmp_table.baseoid = base_oid - return true, snmp_table + return true, snmp_table end diff --git a/nselib/socks.lua b/nselib/socks.lua index 4e53bf17e..6daca7a3a 100644 --- a/nselib/socks.lua +++ b/nselib/socks.lua @@ -12,347 +12,347 @@ _ENV = stdnse.module("socks", stdnse.seeall) -- SOCKS Authentication methods AuthMethod = { - NONE = 0, - GSSAPI = 1, - USERPASS = 2, + NONE = 0, + GSSAPI = 1, + USERPASS = 2, } Request = { - -- Class that handles the connection request to the server - Connect = { + -- Class that handles the connection request to the server + Connect = { - -- Creates a new instance of the class - -- @param auth_method table of requested authentication methods - -- @return o instance on success, nil on failure - new = function(self, auth_method) - local o = { - version = 5, - auth_method = ( "table" ~= type(auth_method) and { auth_method } or auth_method ) - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of the class + -- @param auth_method table of requested authentication methods + -- @return o instance on success, nil on failure + new = function(self, auth_method) + local o = { + version = 5, + auth_method = ( "table" ~= type(auth_method) and { auth_method } or auth_method ) + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the instance to string, so that it can be sent to the - -- server. - -- @return string containing the raw request - __tostring = function(self) - local methods = "" - for _, m in ipairs(self.auth_method) do - methods = methods .. string.char(m) - end - return bin.pack("Cp", self.version, methods) - end, + -- Converts the instance to string, so that it can be sent to the + -- server. + -- @return string containing the raw request + __tostring = function(self) + local methods = "" + for _, m in ipairs(self.auth_method) do + methods = methods .. string.char(m) + end + return bin.pack("Cp", self.version, methods) + end, - }, + }, - -- Class that handles the authentication request to the server - Authenticate = { + -- Class that handles the authentication request to the server + Authenticate = { - -- Creates a new instance of the class - -- @param auth_method number with the requested authentication method - -- @param creds method specific table of credentials - -- currently only user and pass authentication is supported - -- this method requires two fields to be present - -- username and password - -- @return o instance on success, nil on failure - new = function(self, auth_method, creds) - local o = { - auth_method = auth_method, - creds = creds - } - setmetatable(o, self) - self.__index = self - if ( auth_method == 2 ) then - return o - end - end, + -- Creates a new instance of the class + -- @param auth_method number with the requested authentication method + -- @param creds method specific table of credentials + -- currently only user and pass authentication is supported + -- this method requires two fields to be present + -- username and password + -- @return o instance on success, nil on failure + new = function(self, auth_method, creds) + local o = { + auth_method = auth_method, + creds = creds + } + setmetatable(o, self) + self.__index = self + if ( auth_method == 2 ) then + return o + end + end, - -- Converts the instance to string, so that it can be sent to the - -- server. - -- @return string containing the raw request - __tostring = function(self) - -- we really don't support anything but 2, but let's pretend that - -- we actually do - if ( 2 == self.auth_method ) then - local version = 1 - local username= self.creds.username or "" - local password= self.creds.password or "" + -- Converts the instance to string, so that it can be sent to the + -- server. + -- @return string containing the raw request + __tostring = function(self) + -- we really don't support anything but 2, but let's pretend that + -- we actually do + if ( 2 == self.auth_method ) then + local version = 1 + local username= self.creds.username or "" + local password= self.creds.password or "" - username = (username == "") and "\0" or username - password = (password == "") and "\0" or password + username = (username == "") and "\0" or username + password = (password == "") and "\0" or password - return bin.pack("Cpp", version, username, password) - end - end, + return bin.pack("Cpp", version, username, password) + end + end, - } + } } Response = { - -- Class that handles the connection response - Connect = { + -- Class that handles the connection response + Connect = { - -- Creates a new instance of the class - -- @param data string containing the data as received over the socket - -- @return o instance on success, nil on failure - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- Creates a new instance of the class + -- @param data string containing the data as received over the socket + -- @return o instance on success, nil on failure + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- Parses the received data and populates member variables - -- @return true on success, false on failure - parse = function(self) - if ( #self.data ~= 2 ) then - return - end - local pos - pos, self.version, self.method = bin.unpack("CC", self.data) - return true - end + -- Parses the received data and populates member variables + -- @return true on success, false on failure + parse = function(self) + if ( #self.data ~= 2 ) then + return + end + local pos + pos, self.version, self.method = bin.unpack("CC", self.data) + return true + end - }, + }, - -- Class that handles the authentication response - Authenticate = { + -- Class that handles the authentication response + Authenticate = { - Status = { - SUCCESS = 0, - -- could be anything but zero - FAIL = 1, - }, + Status = { + SUCCESS = 0, + -- could be anything but zero + FAIL = 1, + }, - -- Creates a new instance of the class - -- @param data string containing the data as received over the socket - -- @return o instance on success, nil on failure - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- Creates a new instance of the class + -- @param data string containing the data as received over the socket + -- @return o instance on success, nil on failure + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- Parses the received data and populates member variables - -- @return true on success, false on failure - parse = function(self) - if ( #self.data ~= 2 ) then - return - end - local pos - pos, self.version, self.status = bin.unpack("CC", self.data) - return true - end, + -- Parses the received data and populates member variables + -- @return true on success, false on failure + parse = function(self) + if ( #self.data ~= 2 ) then + return + end + local pos + pos, self.version, self.status = bin.unpack("CC", self.data) + return true + end, - -- checks if the authentication was successful or not - -- @return true on success, false on failure - isSuccess = function(self) - return ( self.status == self.Status.SUCCESS ) - end, + -- checks if the authentication was successful or not + -- @return true on success, false on failure + isSuccess = function(self) + return ( self.status == self.Status.SUCCESS ) + end, - } + } } -- A buffered socket implementation Socket = { - retries = 3, + retries = 3, - -- Creates a new socket instance - -- @param host table containing the host table - -- @param port table containing the port table - -- @param options table containing options, currenlty supports: - -- timeout - socket timeout in ms - -- @return o new instance of Socket - new = function(self, host, port, options) - local o = { - host = host, - port = port, - options = options or {} - } - setmetatable(o, self) - self.__index = self - o.Socket = nmap.new_socket() - o.Buffer = nil - return o - end, + -- Creates a new socket instance + -- @param host table containing the host table + -- @param port table containing the port table + -- @param options table containing options, currenlty supports: + -- timeout - socket timeout in ms + -- @return o new instance of Socket + new = function(self, host, port, options) + local o = { + host = host, + port = port, + options = options or {} + } + setmetatable(o, self) + self.__index = self + o.Socket = nmap.new_socket() + o.Buffer = nil + return o + end, - -- Connects the socket to the server - -- @return status true on success false on failure - -- @return err string containing error message on failure - connect = function( self ) - self.Socket:set_timeout(self.options.timeout or 10000) - return self.Socket:connect( self.host, self.port ) - end, + -- Connects the socket to the server + -- @return status true on success false on failure + -- @return err string containing error message on failure + connect = function( self ) + self.Socket:set_timeout(self.options.timeout or 10000) + return self.Socket:connect( self.host, self.port ) + end, - -- Closes an open connection. - -- - -- @return Status (true or false). - -- @return Error code (if status is false). - close = function( self ) - return self.Socket:close() - end, + -- Closes an open connection. + -- + -- @return Status (true or false). + -- @return Error code (if status is false). + close = function( self ) + return self.Socket:close() + end, - -- Opposed to the socket:receive_bytes function, that returns - -- at least x bytes, this function returns the amount of bytes requested. - -- - -- @param count of bytes to read - -- @return true on success, false on failure - -- @return data containing bytes read from the socket - -- err containing error message if status is false - recv = function( self, count ) - local status, data + -- Opposed to the socket:receive_bytes function, that returns + -- at least x bytes, this function returns the amount of bytes requested. + -- + -- @param count of bytes to read + -- @return true on success, false on failure + -- @return data containing bytes read from the socket + -- err containing error message if status is false + recv = function( self, count ) + local status, data - self.Buffer = self.Buffer or "" + self.Buffer = self.Buffer or "" - if ( #self.Buffer < count ) then - status, data = self.Socket:receive_bytes( count - #self.Buffer ) - if ( not(status) or #data < count - #self.Buffer ) then - return false, data - end - self.Buffer = self.Buffer .. data - end + if ( #self.Buffer < count ) then + status, data = self.Socket:receive_bytes( count - #self.Buffer ) + if ( not(status) or #data < count - #self.Buffer ) then + return false, data + end + self.Buffer = self.Buffer .. data + end - data = self.Buffer:sub( 1, count ) - self.Buffer = self.Buffer:sub( count + 1) + data = self.Buffer:sub( 1, count ) + self.Buffer = self.Buffer:sub( count + 1) - return true, data - end, + return true, data + end, - -- Sends data over the socket - -- - -- @return Status (true or false). - -- @return Error code (if status is false). - send = function( self, data ) - return self.Socket:send( data ) - end, + -- Sends data over the socket + -- + -- @return Status (true or false). + -- @return Error code (if status is false). + send = function( self, data ) + return self.Socket:send( data ) + end, } -- The main script interface Helper = { - -- Create a new instance of the class - -- @param host table containing the host table - -- @param port table containing the port table - -- @param options table containing library options, currenlty: - -- timeout - socket timeout in ms - -- @return o instance of Helper - new = function(self, host, port, options) - local o = { host = host, port = port, options = options } - setmetatable(o, self) - self.__index = self - return o - end, + -- Create a new instance of the class + -- @param host table containing the host table + -- @param port table containing the port table + -- @param options table containing library options, currenlty: + -- timeout - socket timeout in ms + -- @return o instance of Helper + new = function(self, host, port, options) + local o = { host = host, port = port, options = options } + setmetatable(o, self) + self.__index = self + return o + end, - -- Get the authentication method name by number - -- @param method number containing the authentication method - -- @return string containing the method name or Unknown - authNameByNumber = function(self, method) - local methods = { - [0] = "No authentication", - [1] = "GSSAPI", - [2] = "Username and password", - } - return methods[method] or ("Unknown method (%d)"):format(method) - end, + -- Get the authentication method name by number + -- @param method number containing the authentication method + -- @return string containing the method name or Unknown + authNameByNumber = function(self, method) + local methods = { + [0] = "No authentication", + [1] = "GSSAPI", + [2] = "Username and password", + } + return methods[method] or ("Unknown method (%d)"):format(method) + end, - -- Connects to the SOCKS server - -- @param auth_method table containing the auth. methods to request - -- @return status true on success, false on failure - -- @return response table containing the respons or err string on failure - connect = function(self, auth_method) - self.socket = Socket:new(self.host, self.port, self.options) - local status, err = self.socket:connect() - if ( not(status) ) then - return status, err - end + -- Connects to the SOCKS server + -- @param auth_method table containing the auth. methods to request + -- @return status true on success, false on failure + -- @return response table containing the respons or err string on failure + connect = function(self, auth_method) + self.socket = Socket:new(self.host, self.port, self.options) + local status, err = self.socket:connect() + if ( not(status) ) then + return status, err + end - auth_method = auth_method or {AuthMethod.NONE, AuthMethod.GSSAPI, AuthMethod.USERPASS} - status = self.socket:send( tostring(Request.Connect:new(auth_method)) ) - if ( not(status) ) then - self.socket:close() - return false, "Failed to send connection request to server" - end + auth_method = auth_method or {AuthMethod.NONE, AuthMethod.GSSAPI, AuthMethod.USERPASS} + status = self.socket:send( tostring(Request.Connect:new(auth_method)) ) + if ( not(status) ) then + self.socket:close() + return false, "Failed to send connection request to server" + end - local status, data = self.socket:recv(2) - if ( not(status) ) then - self.socket:close() - return false, "Failed to receive connection response from server" - end + local status, data = self.socket:recv(2) + if ( not(status) ) then + self.socket:close() + return false, "Failed to receive connection response from server" + end - local response = Response.Connect:new(data) - if ( not(response) ) then - return false, "Failed to parse response from server" - end + local response = Response.Connect:new(data) + if ( not(response) ) then + return false, "Failed to parse response from server" + end - if ( response.version ~= 5 ) then - return false, ("Unsupported SOCKS version (%d)"):format(response.version) - end - if ( response.method == 0xFF ) then - return false, "No acceptable authentication methods" - end + if ( response.version ~= 5 ) then + return false, ("Unsupported SOCKS version (%d)"):format(response.version) + end + if ( response.method == 0xFF ) then + return false, "No acceptable authentication methods" + end - -- store the method so authenticate knows what to use - self.auth_method = response.method - return true, response - end, + -- store the method so authenticate knows what to use + self.auth_method = response.method + return true, response + end, - -- Authenticates to the SOCKS server - -- @param creds table containing authentication method specific fields - -- currently only authentication method 2 (username and pass) is - -- implemented. That method requires the following fields: - -- username - containing the username - -- password - containing the password - -- @return status true on success, false on failure - -- @return err string containing the error message - authenticate = function(self, creds) - if ( self.auth_method ~= 2 ) then - return false, "Authentication method not supported" - end - local req = Request.Authenticate:new(self.auth_method, creds) - if ( not(req) ) then - return false, "Failed to create authentication request" - end + -- Authenticates to the SOCKS server + -- @param creds table containing authentication method specific fields + -- currently only authentication method 2 (username and pass) is + -- implemented. That method requires the following fields: + -- username - containing the username + -- password - containing the password + -- @return status true on success, false on failure + -- @return err string containing the error message + authenticate = function(self, creds) + if ( self.auth_method ~= 2 ) then + return false, "Authentication method not supported" + end + local req = Request.Authenticate:new(self.auth_method, creds) + if ( not(req) ) then + return false, "Failed to create authentication request" + end - local status = self.socket:send(tostring(req)) - if ( not(status) ) then - return false, "Failed to send authentication request" - end + local status = self.socket:send(tostring(req)) + if ( not(status) ) then + return false, "Failed to send authentication request" + end - if ( 2 == self.auth_method ) then - local status, data = self.socket:recv(2) - local auth = Response.Authenticate:new(data) + if ( 2 == self.auth_method ) then + local status, data = self.socket:recv(2) + local auth = Response.Authenticate:new(data) - if ( not(auth) ) then - return false, "Failed to parse authentication response" - end + if ( not(auth) ) then + return false, "Failed to parse authentication response" + end - if ( auth:isSuccess() ) then - return true, "Authentication was successfull" - else - return false, "Authentication failed" - end + if ( auth:isSuccess() ) then + return true, "Authentication was successfull" + else + return false, "Authentication failed" + end - end - return false, "Unsupported authentication method" - end, + end + return false, "Unsupported authentication method" + end, - -- closes the connection to the server - close = function(self) - return self.socket:close() - end, + -- closes the connection to the server + close = function(self) + return self.socket:close() + end, } diff --git a/nselib/srvloc.lua b/nselib/srvloc.lua index e6c26fe0b..9277199a7 100644 --- a/nselib/srvloc.lua +++ b/nselib/srvloc.lua @@ -4,26 +4,26 @@ -- -- The implementation is based on the following classes: -- * Request.Service --- - Contains necessary code to produce a service request +-- - Contains necessary code to produce a service request -- -- * Request.Attributes --- - Contains necessary code to produce a attribute request +-- - Contains necessary code to produce a attribute request -- -- * Reply.Service --- - Contains necessary code to process and parse the response to the --- service request +-- - Contains necessary code to process and parse the response to the +-- service request -- -- * Reply.Attributes --- - Contains necessary code to process and parse the response to the --- attribute request +-- - Contains necessary code to process and parse the response to the +-- attribute request -- -- The following code illustrates intended use of the library: -- --- --- local helper = srvloc.Helper:new() --- local status, tree = helper:ServiceRequest("ndap.novell", "DEFAULT") --- if ( status ) then tree = tree:match("%/%/%/(.*)%.$") end --- +-- +-- local helper = srvloc.Helper:new() +-- local status, tree = helper:ServiceRequest("ndap.novell", "DEFAULT") +-- if ( status ) then tree = tree:match("%/%/%/(.*)%.$") end +-- --@author Patrik Karlsson --@copyright Same as Nmap--See http://nmap.org/book/man-legal.html @@ -39,258 +39,258 @@ local table = require "table" _ENV = stdnse.module("srvloc", stdnse.seeall) PacketFunction = { - SERVICE_REQUEST = 1, - SERVICE_REPLY = 2, - ATTRIB_REQUEST = 6, + SERVICE_REQUEST = 1, + SERVICE_REPLY = 2, + ATTRIB_REQUEST = 6, } Reply = { - Service = { + Service = { - --- Creates a new instance of the Reply.Service class - -- @param data string containing the raw reply as read from the socket - -- @return o instance of Reply.Service - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - o:parse(data) - return o - end, + --- Creates a new instance of the Reply.Service class + -- @param data string containing the raw reply as read from the socket + -- @return o instance of Reply.Service + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + o:parse(data) + return o + end, - --- Parses the service reply raw packet data - -- @param data string containing the raw reply as read from the socket - parse = function(self, data) - local pos - local len_hi, len_lo + --- Parses the service reply raw packet data + -- @param data string containing the raw reply as read from the socket + parse = function(self, data) + local pos + local len_hi, len_lo - pos, self.version, self.func, len_hi, len_lo = bin.unpack(">CCCS", data) - self.len = bit.lshift(len_hi, 16) + len_lo - pos, self.flags = bin.unpack(">S", data, pos) + pos, self.version, self.func, len_hi, len_lo = bin.unpack(">CCCS", data) + self.len = bit.lshift(len_hi, 16) + len_lo + pos, self.flags = bin.unpack(">S", data, pos) - local neo_hi, neo_lo - pos, neo_hi, neo_lo = bin.unpack(">CS", data, pos) - self.next_extension_offset = bit.lshift(neo_hi, 16) + neo_lo + local neo_hi, neo_lo + pos, neo_hi, neo_lo = bin.unpack(">CS", data, pos) + self.next_extension_offset = bit.lshift(neo_hi, 16) + neo_lo - local lang_tag_len - pos, self.xid, lang_tag_len = bin.unpack(">SS", data, pos) - pos, self.lang_tag = bin.unpack("A" .. lang_tag_len, data, pos) + local lang_tag_len + pos, self.xid, lang_tag_len = bin.unpack(">SS", data, pos) + pos, self.lang_tag = bin.unpack("A" .. lang_tag_len, data, pos) - local no_urls, reserved, url_len - pos, self.error_code, no_urls = bin.unpack(">SS", data, pos) + local no_urls, reserved, url_len + pos, self.error_code, no_urls = bin.unpack(">SS", data, pos) - if ( no_urls > 0 ) then - pos, reserved, self.url_lifetime, url_len = bin.unpack(">CSS", data, pos) + if ( no_urls > 0 ) then + pos, reserved, self.url_lifetime, url_len = bin.unpack(">CSS", data, pos) - local num_auths - pos, self.url, num_auths = bin.unpack("A" .. url_len .. "C", data, pos) - end - end, + local num_auths + pos, self.url, num_auths = bin.unpack("A" .. url_len .. "C", data, pos) + end + end, - --- Attempts to create an instance by reading data off the socket - -- @param socket socket conected to the SRVLOC service - -- @return new instance of the Reply.Service class - fromSocket = function(socket) - local status, data = socket:receive() - if ( not(status) ) then return end - return Reply.Service:new(data) - end, + --- Attempts to create an instance by reading data off the socket + -- @param socket socket conected to the SRVLOC service + -- @return new instance of the Reply.Service class + fromSocket = function(socket) + local status, data = socket:receive() + if ( not(status) ) then return end + return Reply.Service:new(data) + end, - --- Gets the url value from the reply - -- @return uri string containing the reply url - getUrl = function(self) return self.url end, - }, + --- Gets the url value from the reply + -- @return uri string containing the reply url + getUrl = function(self) return self.url end, + }, - Attribute = { + Attribute = { - --- Creates a new instance of Reply.Attribute - -- @param data string containing the raw reply as read from the socket - -- @return o instance of Reply.Attribute - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - o:parse(data) - return o - end, + --- Creates a new instance of Reply.Attribute + -- @param data string containing the raw reply as read from the socket + -- @return o instance of Reply.Attribute + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + o:parse(data) + return o + end, - --- Parses the service reply raw packet data - -- @param data string containing the raw reply as read from the socket - parse = function(self, data) - local pos - local len_hi, len_lo + --- Parses the service reply raw packet data + -- @param data string containing the raw reply as read from the socket + parse = function(self, data) + local pos + local len_hi, len_lo - pos, self.version, self.func, len_hi, len_lo = bin.unpack(">CCCS", data) - self.len = bit.lshift(len_hi, 16) + len_lo - pos, self.flags = bin.unpack(">S", data, pos) + pos, self.version, self.func, len_hi, len_lo = bin.unpack(">CCCS", data) + self.len = bit.lshift(len_hi, 16) + len_lo + pos, self.flags = bin.unpack(">S", data, pos) - local neo_hi, neo_lo - pos, neo_hi, neo_lo = bin.unpack(">CS", data, pos) - self.next_extension_offset = bit.lshift(neo_hi, 16) + neo_lo + local neo_hi, neo_lo + pos, neo_hi, neo_lo = bin.unpack(">CS", data, pos) + self.next_extension_offset = bit.lshift(neo_hi, 16) + neo_lo - local lang_tag_len - pos, self.xid, lang_tag_len = bin.unpack(">SS", data, pos) - pos, self.lang_tag = bin.unpack("A" .. lang_tag_len, data, pos) + local lang_tag_len + pos, self.xid, lang_tag_len = bin.unpack(">SS", data, pos) + pos, self.lang_tag = bin.unpack("A" .. lang_tag_len, data, pos) - local attrib_list_len - pos, self.error_code, attrib_list_len = bin.unpack(">SS", data, pos) + local attrib_list_len + pos, self.error_code, attrib_list_len = bin.unpack(">SS", data, pos) - pos, self.attrib_list = bin.unpack("A"..attrib_list_len, data, pos) + pos, self.attrib_list = bin.unpack("A"..attrib_list_len, data, pos) - local num_auths - pos, num_auths = bin.unpack("C", data, pos) - end, + local num_auths + pos, num_auths = bin.unpack("C", data, pos) + end, - --- Attempts to create an instance by reading data off the socket - -- @param socket socket conected to the SRVLOC service - -- @return new instance of the Reply.Attribute class - fromSocket = function(socket) - local status, data = socket:receive() - if ( not(status) ) then return end - return Reply.Attribute:new(data) - end, + --- Attempts to create an instance by reading data off the socket + -- @param socket socket conected to the SRVLOC service + -- @return new instance of the Reply.Attribute class + fromSocket = function(socket) + local status, data = socket:receive() + if ( not(status) ) then return end + return Reply.Attribute:new(data) + end, - --- Gets the attribute list - -- @return attrib_list - getAttribList = function(self) return self.attrib_list end, - } + --- Gets the attribute list + -- @return attrib_list + getAttribList = function(self) return self.attrib_list end, + } } Request = { - -- The attribute request - Attribute = { + -- The attribute request + Attribute = { - --- Creates a new instance of the Attribue request - -- @return o instance of Attribute - new = function(self) - local o = { - lang_tag = "en", version = 2, service_type = "", - scope = "", next_extension_offset = 0, - prev_resp_list_len = 0, slp_spi_len = 0 } - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new instance of the Attribue request + -- @return o instance of Attribute + new = function(self) + local o = { + lang_tag = "en", version = 2, service_type = "", + scope = "", next_extension_offset = 0, + prev_resp_list_len = 0, slp_spi_len = 0 } + setmetatable(o, self) + self.__index = self + return o + end, - --- Sets the request scope - -- @param scope string containing the request scope - setScope = function(self, scope) self.scope = scope end, + --- Sets the request scope + -- @param scope string containing the request scope + setScope = function(self, scope) self.scope = scope end, - --- Sets the language tag - -- @param lang string containing the language - setLangTag = function(self, lang) self.lang_tag = lang end, + --- Sets the language tag + -- @param lang string containing the language + setLangTag = function(self, lang) self.lang_tag = lang end, - --- Sets the request flags - -- @param flags number containing the numeric flag representation - setFlags = function(self, flags) self.flags = flags end, + --- Sets the request flags + -- @param flags number containing the numeric flag representation + setFlags = function(self, flags) self.flags = flags end, - --- Sets the request XID - -- @param xid number containing the request XID - setXID = function(self, xid) self.xid = xid end, + --- Sets the request XID + -- @param xid number containing the request XID + setXID = function(self, xid) self.xid = xid end, - --- Sets the request function - -- @param func number containing the request function number - setFunction = function(self, func) self.func = func end, + --- Sets the request function + -- @param func number containing the request function number + setFunction = function(self, func) self.func = func end, - --- Sets the request taglist - -- @param tl string containing the taglist - setTagList = function(self, tl) self.tag_list = tl end, + --- Sets the request taglist + -- @param tl string containing the taglist + setTagList = function(self, tl) self.tag_list = tl end, - --- Sets the request url - -- @param u string containing the url - setUrl = function(self, u) self.url = u end, + --- Sets the request url + -- @param u string containing the url + setUrl = function(self, u) self.url = u end, - --- "Serializes" the request to a string - -- @return data string containing a string representation of the request - __tostring = function(self) - assert(self.func, "Packet function was not specified") - assert(self.scope, "Packet scope was not specified") + --- "Serializes" the request to a string + -- @return data string containing a string representation of the request + __tostring = function(self) + assert(self.func, "Packet function was not specified") + assert(self.scope, "Packet scope was not specified") - local BASE_LEN = 24 - local len = BASE_LEN + #self.lang_tag + self.prev_resp_list_len + - self.slp_spi_len + #self.service_type + #self.url + - #self.tag_list + #self.scope - local len_hi = bit.band(bit.rshift(len, 16), 0x00FF) - local len_lo = bit.band(len, 0xFFFF) - local neo_hi = bit.band(bit.rshift(self.next_extension_offset, 16), - 0x00FF) - local neo_lo = bit.band(self.next_extension_offset, 0xFFFF) + local BASE_LEN = 24 + local len = BASE_LEN + #self.lang_tag + self.prev_resp_list_len + + self.slp_spi_len + #self.service_type + #self.url + + #self.tag_list + #self.scope + local len_hi = bit.band(bit.rshift(len, 16), 0x00FF) + local len_lo = bit.band(len, 0xFFFF) + local neo_hi = bit.band(bit.rshift(self.next_extension_offset, 16), + 0x00FF) + local neo_lo = bit.band(self.next_extension_offset, 0xFFFF) - local data = bin.pack(">CCCSSCSSSASSASASAS", self.version, self.func, - len_hi, len_lo, self.flags, neo_hi, neo_lo, self.xid, #self.lang_tag, self.lang_tag, - self.prev_resp_list_len, #self.url, self.url, #self.scope, self.scope, - #self.tag_list, self.tag_list, self.slp_spi_len) + local data = bin.pack(">CCCSSCSSSASSASASAS", self.version, self.func, + len_hi, len_lo, self.flags, neo_hi, neo_lo, self.xid, #self.lang_tag, self.lang_tag, + self.prev_resp_list_len, #self.url, self.url, #self.scope, self.scope, + #self.tag_list, self.tag_list, self.slp_spi_len) - return data - end - }, + return data + end + }, - -- The Service request - Service = { + -- The Service request + Service = { - --- Creates a new instance of the Service request - -- @return o instance of Service - new = function(self) - local o = { - lang_tag = "en", version = 2, service_type = "", - scope = "", next_extension_offset = 0, - prev_resp_list_len = 0, predicate_len = 0, slp_spi_len = 0 } - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new instance of the Service request + -- @return o instance of Service + new = function(self) + local o = { + lang_tag = "en", version = 2, service_type = "", + scope = "", next_extension_offset = 0, + prev_resp_list_len = 0, predicate_len = 0, slp_spi_len = 0 } + setmetatable(o, self) + self.__index = self + return o + end, - --- Sets the service type of the request - -- @param t string containing the type of the request - setServiceType = function(self, t) self.service_type = t end, + --- Sets the service type of the request + -- @param t string containing the type of the request + setServiceType = function(self, t) self.service_type = t end, - --- Sets the request scope - -- @param scope string containing the request scope - setScope = function(self, scope) self.scope = scope end, + --- Sets the request scope + -- @param scope string containing the request scope + setScope = function(self, scope) self.scope = scope end, - --- Sets the language tag - -- @param lang string containing the language - setLangTag = function(self, lang) self.lang_tag = lang end, + --- Sets the language tag + -- @param lang string containing the language + setLangTag = function(self, lang) self.lang_tag = lang end, - --- Sets the request flags - -- @param flags number containing the numeric flag representation - setFlags = function(self, flags) self.flags = flags end, + --- Sets the request flags + -- @param flags number containing the numeric flag representation + setFlags = function(self, flags) self.flags = flags end, - --- Sets the request XID - -- @param xid number containing the request XID - setXID = function(self, xid) self.xid = xid end, + --- Sets the request XID + -- @param xid number containing the request XID + setXID = function(self, xid) self.xid = xid end, - --- Sets the request function - -- @param func number containing the request function number - setFunction = function(self, func) self.func = func end, + --- Sets the request function + -- @param func number containing the request function number + setFunction = function(self, func) self.func = func end, - --- "Serializes" the request to a string - -- @return data string containing a string representation of the request - __tostring = function(self) - assert(self.func, "Packet function was not specified") - assert(self.scope, "Packet scope was not specified") + --- "Serializes" the request to a string + -- @return data string containing a string representation of the request + __tostring = function(self) + assert(self.func, "Packet function was not specified") + assert(self.scope, "Packet scope was not specified") - local BASE_LEN = 24 - local len = BASE_LEN + #self.lang_tag + self.prev_resp_list_len + - self.predicate_len + self.slp_spi_len + #self.service_type + - #self.scope - local len_hi = bit.band(bit.rshift(len, 16), 0x00FF) - local len_lo = bit.band(len, 0xFFFF) - local neo_hi = bit.band(bit.rshift(self.next_extension_offset, 16), - 0x00FF) - local neo_lo = bit.band(self.next_extension_offset, 0xFFFF) + local BASE_LEN = 24 + local len = BASE_LEN + #self.lang_tag + self.prev_resp_list_len + + self.predicate_len + self.slp_spi_len + #self.service_type + + #self.scope + local len_hi = bit.band(bit.rshift(len, 16), 0x00FF) + local len_lo = bit.band(len, 0xFFFF) + local neo_hi = bit.band(bit.rshift(self.next_extension_offset, 16), + 0x00FF) + local neo_lo = bit.band(self.next_extension_offset, 0xFFFF) - local data = bin.pack(">CCCSSCSSSASSASASS", self.version, self.func, - len_hi, len_lo, self.flags, neo_hi, neo_lo, self.xid, #self.lang_tag, self.lang_tag, - self.prev_resp_list_len, #self.service_type, self.service_type, #self.scope, - self.scope, self.predicate_len, self.slp_spi_len) + local data = bin.pack(">CCCSSCSSSASSASASS", self.version, self.func, + len_hi, len_lo, self.flags, neo_hi, neo_lo, self.xid, #self.lang_tag, self.lang_tag, + self.prev_resp_list_len, #self.service_type, self.service_type, #self.scope, + self.scope, self.predicate_len, self.slp_spi_len) - return data - end - } + return data + end + } } @@ -298,81 +298,81 @@ Request = { -- The Helper class serves as primary interface for scripts using the libraryy Helper = { - new = function(self, host, port) - local o = { xid = 1, socket = nmap.new_socket("udp") } - setmetatable(o, self) - self.__index = self - local family = nmap.address_family() - o.host = host or (family=="inet6" and "FF02::116" or "239.255.255.253") - o.port = port or { number=427, proto="udp" } - return o - end, + new = function(self, host, port) + local o = { xid = 1, socket = nmap.new_socket("udp") } + setmetatable(o, self) + self.__index = self + local family = nmap.address_family() + o.host = host or (family=="inet6" and "FF02::116" or "239.255.255.253") + o.port = port or { number=427, proto="udp" } + return o + end, - --- Sends a service request and waits for the response - -- @param srvtype string containing the service type to query - -- @param scope string containing the scope of the request - -- @return true on success, false on failure - -- @return url string (on success) containing the url of the ServiceReply - -- @return err string (on failure) containing the error message - ServiceRequest = function(self, srvtype, scope) - local srvtype = srvtype or "" - local scope = scope or "" - local sr = Request.Service:new() - sr:setXID(self.xid) - sr:setServiceType(srvtype) - sr:setScope(scope) - sr:setFunction(PacketFunction.SERVICE_REQUEST) - sr:setFlags(0x2000) + --- Sends a service request and waits for the response + -- @param srvtype string containing the service type to query + -- @param scope string containing the scope of the request + -- @return true on success, false on failure + -- @return url string (on success) containing the url of the ServiceReply + -- @return err string (on failure) containing the error message + ServiceRequest = function(self, srvtype, scope) + local srvtype = srvtype or "" + local scope = scope or "" + local sr = Request.Service:new() + sr:setXID(self.xid) + sr:setServiceType(srvtype) + sr:setScope(scope) + sr:setFunction(PacketFunction.SERVICE_REQUEST) + sr:setFlags(0x2000) - self.socket:set_timeout(5000) - self.socket:sendto( self.host, self.port, tostring(sr) ) + self.socket:set_timeout(5000) + self.socket:sendto( self.host, self.port, tostring(sr) ) - local result = {} - repeat - local r = Reply.Service.fromSocket(self.socket) - if ( r ) then - table.insert(result, r:getUrl()) - end - self.xid = self.xid + 1 - until(not(r)) + local result = {} + repeat + local r = Reply.Service.fromSocket(self.socket) + if ( r ) then + table.insert(result, r:getUrl()) + end + self.xid = self.xid + 1 + until(not(r)) - if ( #result == 0 ) then - return false, "ERROR: Helper.Locate no response received" - end - return true, result - end, + if ( #result == 0 ) then + return false, "ERROR: Helper.Locate no response received" + end + return true, result + end, - --- Requests an attribute from the server - -- @param url as retrieved by the Service request - -- @param scope string containing the request scope - -- @param taglist string containing the request tag list - AttributeRequest = function(self, url, scope, taglist) - local url = url or "" - local scope = scope or "" - local taglist = taglist or "" - local ar = Request.Attribute:new() - ar:setXID(self.xid) - ar:setScope(scope) - ar:setUrl(url) - ar:setTagList(taglist) - ar:setFunction(PacketFunction.ATTRIB_REQUEST) - ar:setFlags(0x2000) + --- Requests an attribute from the server + -- @param url as retrieved by the Service request + -- @param scope string containing the request scope + -- @param taglist string containing the request tag list + AttributeRequest = function(self, url, scope, taglist) + local url = url or "" + local scope = scope or "" + local taglist = taglist or "" + local ar = Request.Attribute:new() + ar:setXID(self.xid) + ar:setScope(scope) + ar:setUrl(url) + ar:setTagList(taglist) + ar:setFunction(PacketFunction.ATTRIB_REQUEST) + ar:setFlags(0x2000) - self.socket:set_timeout(5000) - self.socket:sendto( self.host, self.port, tostring(ar) ) + self.socket:set_timeout(5000) + self.socket:sendto( self.host, self.port, tostring(ar) ) - local r = Reply.Attribute.fromSocket(self.socket) + local r = Reply.Attribute.fromSocket(self.socket) - self.xid = self.xid + 1 - if ( not(r) ) then - return false, "ERROR: Helper.Locate no response received" - end - return true, r:getAttribList() - end, + self.xid = self.xid + 1 + if ( not(r) ) then + return false, "ERROR: Helper.Locate no response received" + end + return true, r:getAttribList() + end, - close = function(self) - return self.socket:close() - end, + close = function(self) + return self.socket:close() + end, } return _ENV; diff --git a/nselib/sslcert.lua b/nselib/sslcert.lua index 2946571d5..aed2cc9d2 100644 --- a/nselib/sslcert.lua +++ b/nselib/sslcert.lua @@ -16,229 +16,229 @@ _ENV = stdnse.module("sslcert", stdnse.seeall) StartTLS = { - ftp_prepare_tls_without_reconnect = function(host, port) - local s = nmap.new_socket() - -- Attempt to negotiate TLS over FTP for services that support it - -- Works for FTP (21) + ftp_prepare_tls_without_reconnect = function(host, port) + local s = nmap.new_socket() + -- Attempt to negotiate TLS over FTP for services that support it + -- Works for FTP (21) - -- Open a standard TCP socket - local status, error = s:connect(host, port, "tcp") - local result - if not status then - return false, "Failed to connect to FTP server" - else + -- Open a standard TCP socket + local status, error = s:connect(host, port, "tcp") + local result + if not status then + return false, "Failed to connect to FTP server" + else - -- Loop until the service presents a banner to deal with server - -- load and timing issues. There may be a better way to handle this. - local i = 0 - repeat - status, result = s:receive_lines(1) - i = i + 1 - until string.match(result, "^220") or i == 5 + -- Loop until the service presents a banner to deal with server + -- load and timing issues. There may be a better way to handle this. + local i = 0 + repeat + status, result = s:receive_lines(1) + i = i + 1 + until string.match(result, "^220") or i == 5 - -- Send AUTH TLS command, ask the service to start encryption - local query = "AUTH TLS\r\n" - status = s:send(query) - status, result = s:receive_lines(1) + -- Send AUTH TLS command, ask the service to start encryption + local query = "AUTH TLS\r\n" + status = s:send(query) + status, result = s:receive_lines(1) - if not (string.match(result, "^234")) then - stdnse.print_debug("1","%s",result) - stdnse.print_debug("1","AUTH TLS failed or unavailable. Enable --script-trace to see what is happening.") + if not (string.match(result, "^234")) then + stdnse.print_debug("1","%s",result) + stdnse.print_debug("1","AUTH TLS failed or unavailable. Enable --script-trace to see what is happening.") - -- Send QUIT to clean up server side connection - local query = "QUIT\r\n" - status = s:send(query) - result = "" + -- Send QUIT to clean up server side connection + local query = "QUIT\r\n" + status = s:send(query) + result = "" - return false, "Failed to connect to FTP server" - end - end - -- Should have a solid TLS over FTP session now... - return true, s - end, + return false, "Failed to connect to FTP server" + end + end + -- Should have a solid TLS over FTP session now... + return true, s + end, - ftp_prepare_tls = function(host, port) - local err - local status, s = StartTLS.ftp_prepare_tls_without_reconnect(host, port) - if status then - status,err = s:reconnect_ssl() - if not status then - stdnse.print_debug("1","Could not establish SSL session after STARTTLS command.") - s:close() - return false, "Failed to connect to SMTP server" - else - return true,s - end - end - return false, "Failed to connect to FTP server" - end, + ftp_prepare_tls = function(host, port) + local err + local status, s = StartTLS.ftp_prepare_tls_without_reconnect(host, port) + if status then + status,err = s:reconnect_ssl() + if not status then + stdnse.print_debug("1","Could not establish SSL session after STARTTLS command.") + s:close() + return false, "Failed to connect to SMTP server" + else + return true,s + end + end + return false, "Failed to connect to FTP server" + end, - smtp_prepare_tls_without_reconnect = function(host, port) - local s = nmap.new_socket() - -- Attempt to negotiate TLS over SMTP for services that support it - -- Works for SMTP (25) and SMTP Submission (587) + smtp_prepare_tls_without_reconnect = function(host, port) + local s = nmap.new_socket() + -- Attempt to negotiate TLS over SMTP for services that support it + -- Works for SMTP (25) and SMTP Submission (587) - -- Open a standard TCP socket - local status, error = s:connect(host, port, "tcp") + -- Open a standard TCP socket + local status, error = s:connect(host, port, "tcp") - if not status then - return nil - else - local resultEHLO - -- Loop until the service presents a banner to deal with server - -- load and timing issues. There may be a better way to handle this. - local i = 0 - repeat - status, resultEHLO = s:receive_lines(1) - i = i + 1 - until string.match(resultEHLO, "^220") or i == 5 + if not status then + return nil + else + local resultEHLO + -- Loop until the service presents a banner to deal with server + -- load and timing issues. There may be a better way to handle this. + local i = 0 + repeat + status, resultEHLO = s:receive_lines(1) + i = i + 1 + until string.match(resultEHLO, "^220") or i == 5 - -- Send EHLO because the the server expects it - -- We are not going to check for STARTTLS in the capabilities - -- list, sometimes it is not advertised. - local query = "EHLO example.org\r\n" - status = s:send(query) - status, resultEHLO = s:receive_lines(1) + -- Send EHLO because the the server expects it + -- We are not going to check for STARTTLS in the capabilities + -- list, sometimes it is not advertised. + local query = "EHLO example.org\r\n" + status = s:send(query) + status, resultEHLO = s:receive_lines(1) - if not (string.match(resultEHLO, "^250")) then - stdnse.print_debug("1","%s",resultEHLO) - stdnse.print_debug("1","EHLO with errors or timeout. Enable --script-trace to see what is happening.") - return false, "Failed to connect to SMTP server" - end + if not (string.match(resultEHLO, "^250")) then + stdnse.print_debug("1","%s",resultEHLO) + stdnse.print_debug("1","EHLO with errors or timeout. Enable --script-trace to see what is happening.") + return false, "Failed to connect to SMTP server" + end - resultEHLO = "" + resultEHLO = "" - -- Send STARTTLS command ask the service to start encryption - local query = "STARTTLS\r\n" - status = s:send(query) - status, resultEHLO = s:receive_lines(1) + -- Send STARTTLS command ask the service to start encryption + local query = "STARTTLS\r\n" + status = s:send(query) + status, resultEHLO = s:receive_lines(1) - if not (string.match(resultEHLO, "^220")) then - stdnse.print_debug("1","%s",resultEHLO) - stdnse.print_debug("1","STARTTLS failed or unavailable. Enable --script-trace to see what is happening.") + if not (string.match(resultEHLO, "^220")) then + stdnse.print_debug("1","%s",resultEHLO) + stdnse.print_debug("1","STARTTLS failed or unavailable. Enable --script-trace to see what is happening.") - -- Send QUIT to clean up server side connection - local query = "QUIT\r\n" - status = s:send(query) - resultEHLO = "" + -- Send QUIT to clean up server side connection + local query = "QUIT\r\n" + status = s:send(query) + resultEHLO = "" - return false, "Failed to connect to SMTP server" - end - end - -- Should have a solid TLS over SMTP session now... - return true, s - end, + return false, "Failed to connect to SMTP server" + end + end + -- Should have a solid TLS over SMTP session now... + return true, s + end, - smtp_prepare_tls = function(host, port) - local err - local status,s = StartTLS.smtp_prepare_tls_without_reconnect(host, port) - if status then - status,err = s:reconnect_ssl() - if not status then - stdnse.print_debug("1","Could not establish SSL session after STARTTLS command.") - s:close() - return false, "Failed to connect to SMTP server" - else - return true,s - end - end - return false, "Failed to connect to SMTP server" - end, + smtp_prepare_tls = function(host, port) + local err + local status,s = StartTLS.smtp_prepare_tls_without_reconnect(host, port) + if status then + status,err = s:reconnect_ssl() + if not status then + stdnse.print_debug("1","Could not establish SSL session after STARTTLS command.") + s:close() + return false, "Failed to connect to SMTP server" + else + return true,s + end + end + return false, "Failed to connect to SMTP server" + end, - xmpp_prepare_tls_without_reconnect = function(host,port) - local sock,status,err,result - local xmppStreamStart = string.format("\r\n\r\n",host.name) - local xmppStartTLS = "\r\n" - sock = nmap.new_socket() - sock:set_timeout(5000) - status, err = sock:connect(host, port) - if not status then - sock:close() - stdnse.print_debug("Can't send: %s", err) - return false, "Failed to connect to XMPP server" - end - status, err = sock:send(xmppStreamStart) - if not status then - stdnse.print_debug("Couldn't send: %s", err) - sock:close() - return false, "Failed to connect to XMPP server" - end - status, result = sock:receive() - if not status then - stdnse.print_debug("Couldn't receive: %s", err) - sock:close() - return false, "Failed to connect to XMPP server" - end - status, err = sock:send(xmppStartTLS) - if not status then - stdnse.print_debug("Couldn't send: %s", err) - sock:close() - return false, "Failed to connect to XMPP server" - end - status, result = sock:receive() - if not status then - stdnse.print_debug("Couldn't receive: %s", err) - sock:close() - return false, "Failed to connect to XMPP server" - end - if string.find(result,"proceed") then - return true,sock - end + xmpp_prepare_tls_without_reconnect = function(host,port) + local sock,status,err,result + local xmppStreamStart = string.format("\r\n\r\n",host.name) + local xmppStartTLS = "\r\n" + sock = nmap.new_socket() + sock:set_timeout(5000) + status, err = sock:connect(host, port) + if not status then + sock:close() + stdnse.print_debug("Can't send: %s", err) + return false, "Failed to connect to XMPP server" + end + status, err = sock:send(xmppStreamStart) + if not status then + stdnse.print_debug("Couldn't send: %s", err) + sock:close() + return false, "Failed to connect to XMPP server" + end + status, result = sock:receive() + if not status then + stdnse.print_debug("Couldn't receive: %s", err) + sock:close() + return false, "Failed to connect to XMPP server" + end + status, err = sock:send(xmppStartTLS) + if not status then + stdnse.print_debug("Couldn't send: %s", err) + sock:close() + return false, "Failed to connect to XMPP server" + end + status, result = sock:receive() + if not status then + stdnse.print_debug("Couldn't receive: %s", err) + sock:close() + return false, "Failed to connect to XMPP server" + end + if string.find(result,"proceed") then + return true,sock + end - status, result = sock:receive() -- might not be in the first reply - if not status then - stdnse.print_debug("Couldn't receive: %s", err) - sock:close() - return false, "Failed to connect to XMPP server" - end - if string.find(result,"proceed") then - return true,sock - else - return false, "Failed to connect to XMPP server" - end - end, + status, result = sock:receive() -- might not be in the first reply + if not status then + stdnse.print_debug("Couldn't receive: %s", err) + sock:close() + return false, "Failed to connect to XMPP server" + end + if string.find(result,"proceed") then + return true,sock + else + return false, "Failed to connect to XMPP server" + end + end, - xmpp_prepare_tls = function(host, port) - local ls = xmpp.XMPP:new(host, port, { starttls = true } ) - ls.socket = nmap.new_socket() - ls.socket:set_timeout(ls.options.timeout * 1000) + xmpp_prepare_tls = function(host, port) + local ls = xmpp.XMPP:new(host, port, { starttls = true } ) + ls.socket = nmap.new_socket() + ls.socket:set_timeout(ls.options.timeout * 1000) - local status, err = ls.socket:connect(host, port) - if not status then - return nil - end + local status, err = ls.socket:connect(host, port) + if not status then + return nil + end - status, err = ls:connect() - if not(status) then - return false, "Failed to connected" - end - return true, ls.socket - end + status, err = ls:connect() + if not(status) then + return false, "Failed to connected" + end + return true, ls.socket + end } -- A table mapping port numbers to specialized SSL negotiation functions. local SPECIALIZED_PREPARE_TLS = { - [21] = StartTLS.ftp_prepare_tls, - [25] = StartTLS.smtp_prepare_tls, - [587] = StartTLS.smtp_prepare_tls, - [5222] = StartTLS.xmpp_prepare_tls, - [5269] = StartTLS.xmpp_prepare_tls + [21] = StartTLS.ftp_prepare_tls, + [25] = StartTLS.smtp_prepare_tls, + [587] = StartTLS.smtp_prepare_tls, + [5222] = StartTLS.xmpp_prepare_tls, + [5269] = StartTLS.xmpp_prepare_tls } local SPECIALIZED_PREPARE_TLS_WITHOUT_RECONNECT = { - [21] = StartTLS.ftp_prepare_tls_without_reconnect, - [25] = StartTLS.smtp_prepare_tls_without_reconnect, - [587] = StartTLS.smtp_prepare_tls_without_reconnect, - [5222] = StartTLS.xmpp_prepare_tls_without_reconnect, - [5269] = StartTLS.xmpp_prepare_tls_without_reconnect + [21] = StartTLS.ftp_prepare_tls_without_reconnect, + [25] = StartTLS.smtp_prepare_tls_without_reconnect, + [587] = StartTLS.smtp_prepare_tls_without_reconnect, + [5222] = StartTLS.xmpp_prepare_tls_without_reconnect, + [5269] = StartTLS.xmpp_prepare_tls_without_reconnect } function getPrepareTLSWithoutReconnect(port) - return SPECIALIZED_PREPARE_TLS_WITHOUT_RECONNECT[port.number] + return SPECIALIZED_PREPARE_TLS_WITHOUT_RECONNECT[port.number] end function isPortSupported(port) - return SPECIALIZED_PREPARE_TLS[port.number] + return SPECIALIZED_PREPARE_TLS[port.number] end --- Gets a certificate for the given host and port @@ -249,44 +249,44 @@ end -- @return cert userdata containing the SSL certificate, or error message on -- failure. function getCertificate(host, port) - local mutex = nmap.mutex("sslcert-cache-mutex") - mutex "lock" + local mutex = nmap.mutex("sslcert-cache-mutex") + mutex "lock" - if ( host.registry["ssl-cert"] and - host.registry["ssl-cert"][port.number] ) then - stdnse.print_debug(2, "sslcert: Returning cached SSL certificate") - mutex "done" - return true, host.registry["ssl-cert"][port.number] - end + if ( host.registry["ssl-cert"] and + host.registry["ssl-cert"][port.number] ) then + stdnse.print_debug(2, "sslcert: Returning cached SSL certificate") + mutex "done" + return true, host.registry["ssl-cert"][port.number] + end - -- Is there a specialized function for this port? - local specialized = SPECIALIZED_PREPARE_TLS[port.number] - local status - local socket = nmap.new_socket() - if specialized then - status, socket = specialized(host, port) - if not status then - mutex "done" - return false, "Failed to connect to server" - end - else - local status - status = socket:connect(host, port, "ssl") - if ( not(status) ) then - mutex "done" - return false, "Failed to connect to server" - end + -- Is there a specialized function for this port? + local specialized = SPECIALIZED_PREPARE_TLS[port.number] + local status + local socket = nmap.new_socket() + if specialized then + status, socket = specialized(host, port) + if not status then + mutex "done" + return false, "Failed to connect to server" end - local cert = socket:get_ssl_certificate() - if ( cert == nil ) then - return false, "Unable to get cert" - end + else + local status + status = socket:connect(host, port, "ssl") + if ( not(status) ) then + mutex "done" + return false, "Failed to connect to server" + end + end + local cert = socket:get_ssl_certificate() + if ( cert == nil ) then + return false, "Unable to get cert" + end - host.registry["ssl-cert"] = host.registry["ssl-cert"] or {} - host.registry["ssl-cert"][port.number] = host.registry["ssl-cert"][port.number] or {} - host.registry["ssl-cert"][port.number] = cert - mutex "done" - return true, cert + host.registry["ssl-cert"] = host.registry["ssl-cert"] or {} + host.registry["ssl-cert"][port.number] = host.registry["ssl-cert"][port.number] or {} + host.registry["ssl-cert"][port.number] = cert + mutex "done" + return true, cert end diff --git a/nselib/stun.lua b/nselib/stun.lua index af6dd3a8c..40a9dcd11 100644 --- a/nselib/stun.lua +++ b/nselib/stun.lua @@ -19,368 +19,368 @@ _ENV = stdnse.module("stun", stdnse.seeall) -- The supported request types MessageType = { - BINDING_REQUEST = 0x0001, - BINDING_RESPONSE = 0x0101, + BINDING_REQUEST = 0x0001, + BINDING_RESPONSE = 0x0101, } -- The header used in both request and responses Header = { - -- the header size in bytes - size = 20, + -- the header size in bytes + size = 20, - -- creates a new instance of Header - -- @param type number the request/response type - -- @param trans_id string the 128-bit transaction id - -- @param length number the packet length - new = function(self, type, trans_id, length) - local o = { type = type, trans_id = trans_id, length = length or 0 } - setmetatable(o, self) - self.__index = self - return o - end, + -- creates a new instance of Header + -- @param type number the request/response type + -- @param trans_id string the 128-bit transaction id + -- @param length number the packet length + new = function(self, type, trans_id, length) + local o = { type = type, trans_id = trans_id, length = length or 0 } + setmetatable(o, self) + self.__index = self + return o + end, - -- parses an opaque string and creates a new Header instance - -- @param data opaque string - -- @return header new instance of Header - parse = function(data) - local header = Header:new() - local pos - pos, header.type, header.length, header.trans_id = bin.unpack(">SSA16", data) - return header - end, + -- parses an opaque string and creates a new Header instance + -- @param data opaque string + -- @return header new instance of Header + parse = function(data) + local header = Header:new() + local pos + pos, header.type, header.length, header.trans_id = bin.unpack(">SSA16", data) + return header + end, - -- converts the header to an opaque string - -- @return string containing the header instance - __tostring = function(self) - return bin.pack(">SSA", self.type, self.length, self.trans_id) - end, + -- converts the header to an opaque string + -- @return string containing the header instance + __tostring = function(self) + return bin.pack(">SSA", self.type, self.length, self.trans_id) + end, } Request = { - -- The binding request - Bind = { + -- The binding request + Bind = { - -- Creates a new Bind request - -- @param trans_id string containing the 128 bit transaction ID - -- @return o new instance of the Bind request - new = function(self, trans_id) - local o = { - header = Header:new(MessageType.BINDING_REQUEST, trans_id), - attributes = {} - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Bind request + -- @param trans_id string containing the 128 bit transaction ID + -- @return o new instance of the Bind request + new = function(self, trans_id) + local o = { + header = Header:new(MessageType.BINDING_REQUEST, trans_id), + attributes = {} + } + setmetatable(o, self) + self.__index = self + return o + end, - -- converts the instance to an opaque string - -- @return string containing the Bind request as string - __tostring = function(self) - local data = "" - for _, attrib in ipairs(self.attributes) do - data = data .. tostring(attrib) - end - self.header.length = #data - return tostring(self.header) .. data - end, - } + -- converts the instance to an opaque string + -- @return string containing the Bind request as string + __tostring = function(self) + local data = "" + for _, attrib in ipairs(self.attributes) do + data = data .. tostring(attrib) + end + self.header.length = #data + return tostring(self.header) .. data + end, + } } -- The attribute class Attribute = { - MAPPED_ADDRESS = 0x0001, - RESPONSE_ADDRESS = 0x0002, - CHANGE_REQUEST = 0x0003, - SOURCE_ADDRESS = 0x0004, - CHANGED_ADDRESS = 0x0005, - USERNAME = 0x0006, - PASSWORD = 0x0007, - MESSAGE_INTEGRITY = 0x0008, - ERROR_CODE = 0x0009, - UNKNOWN_ATTRIBUTES = 0x000a, - REFLECTED_FROM = 0x000b, - SERVER = 0x8022, + MAPPED_ADDRESS = 0x0001, + RESPONSE_ADDRESS = 0x0002, + CHANGE_REQUEST = 0x0003, + SOURCE_ADDRESS = 0x0004, + CHANGED_ADDRESS = 0x0005, + USERNAME = 0x0006, + PASSWORD = 0x0007, + MESSAGE_INTEGRITY = 0x0008, + ERROR_CODE = 0x0009, + UNKNOWN_ATTRIBUTES = 0x000a, + REFLECTED_FROM = 0x000b, + SERVER = 0x8022, - -- creates a new attribute instance - -- @param type number containing the attribute type - -- @param data string containing the attribute value - -- @return o instance of attribute - new = function(self, type, data) - local o = { - type = type, - length = (data and #data or 0), - data = data, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- creates a new attribute instance + -- @param type number containing the attribute type + -- @param data string containing the attribute value + -- @return o instance of attribute + new = function(self, type, data) + local o = { + type = type, + length = (data and #data or 0), + data = data, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- parses a string and creates an Attribute instance - -- @param data string containing the raw attribute - -- @return o new attribute instance - parse = function(data) - local attr = Attribute:new() - local pos = 1 + -- parses a string and creates an Attribute instance + -- @param data string containing the raw attribute + -- @return o new attribute instance + parse = function(data) + local attr = Attribute:new() + local pos = 1 - pos, attr.type, attr.length = bin.unpack(">SS", data, pos) + pos, attr.type, attr.length = bin.unpack(">SS", data, pos) - local function parseAddress(data, pos) - local _, addr = nil, {} - pos, _, addr.family, addr.port, addr.ip = bin.unpack("SSA", self.type, self.length, self.data or "") - end, + -- converts an attribute to string + -- @return string containing the serialized attribute + __tostring = function(self) + return bin.pack(">SSA", self.type, self.length, self.data or "") + end, } -- Response class container Response = { - -- Bind response class - Bind = { + -- Bind response class + Bind = { - -- creates a new instance of the Bind response - -- @param trans_id string containing the 128 bit transaction id - -- @return o new Bind instance - new = function(self, trans_id) - local o = { header = Header:new(MessageType.BINDING_RESPONSE, trans_id) } - setmetatable(o, self) - self.__index = self - return o - end, + -- creates a new instance of the Bind response + -- @param trans_id string containing the 128 bit transaction id + -- @return o new Bind instance + new = function(self, trans_id) + local o = { header = Header:new(MessageType.BINDING_RESPONSE, trans_id) } + setmetatable(o, self) + self.__index = self + return o + end, - -- parses a raw string and creates a new Bind instance - -- @param data string containing the raw data - -- @return resp containing a new Bind instance - parse = function(data) - local resp = Response.Bind:new() - local pos = Header.size + -- parses a raw string and creates a new Bind instance + -- @param data string containing the raw data + -- @return resp containing a new Bind instance + parse = function(data) + local resp = Response.Bind:new() + local pos = Header.size - resp.header = Header.parse(data) - resp.attributes = {} + resp.header = Header.parse(data) + resp.attributes = {} - while( pos < #data ) do - local attr = Attribute.parse(data:sub(pos)) - table.insert(resp.attributes, attr) - pos = pos + attr.length + 4 - end - return resp - end - } + while( pos < #data ) do + local attr = Attribute.parse(data:sub(pos)) + table.insert(resp.attributes, attr) + pos = pos + attr.length + 4 + end + return resp + end + } } -- The communication class Comm = { - -- creates a new Comm instance - -- @param host table - -- @param port table - -- @param options table, currently supporting: - -- timeout - socket timeout in ms. - -- @param mode containing the mode - -- @return o new instance of Comm - new = function(self, host, port, options, mode) - local o = { - host = host, - port = port, - options = options or { timeout = 10000 }, - socket = nmap.new_socket(), - } - setmetatable(o, self) - self.__index = self - return o - end, + -- creates a new Comm instance + -- @param host table + -- @param port table + -- @param options table, currently supporting: + -- timeout - socket timeout in ms. + -- @param mode containing the mode + -- @return o new instance of Comm + new = function(self, host, port, options, mode) + local o = { + host = host, + port = port, + options = options or { timeout = 10000 }, + socket = nmap.new_socket(), + } + setmetatable(o, self) + self.__index = self + return o + end, - -- connects the socket to the server - -- @return status true on success, false on failure - -- @return err string containing an error message, if status is false - connect = function(self) - self.socket:set_timeout(self.options.timeout) - return self.socket:connect(self.host, self.port) - end, + -- connects the socket to the server + -- @return status true on success, false on failure + -- @return err string containing an error message, if status is false + connect = function(self) + self.socket:set_timeout(self.options.timeout) + return self.socket:connect(self.host, self.port) + end, - -- sends a request to the server - -- @return status true on success, false on failure - -- @return err string containing an error message, if status is false - send = function(self, data) - return self.socket:send(data) - end, + -- sends a request to the server + -- @return status true on success, false on failure + -- @return err string containing an error message, if status is false + send = function(self, data) + return self.socket:send(data) + end, - -- receives a response from the server - -- @return status true on success, false on failure - -- @return response containing a response instance - -- err string containing an error message, if status is false - recv = function(self) - local status, hdr_data = self.socket:receive_buf(match.numbytes(Header.size), false) - if ( not(status) ) then - return false, "Failed to receive response from server" - end + -- receives a response from the server + -- @return status true on success, false on failure + -- @return response containing a response instance + -- err string containing an error message, if status is false + recv = function(self) + local status, hdr_data = self.socket:receive_buf(match.numbytes(Header.size), false) + if ( not(status) ) then + return false, "Failed to receive response from server" + end - local header = Header.parse(hdr_data) - if ( not(header) ) then - return false, "Failed to parse response header" - end + local header = Header.parse(hdr_data) + if ( not(header) ) then + return false, "Failed to parse response header" + end - local status, data = self.socket:receive_buf(match.numbytes(header.length), false) - if ( header.type == MessageType.BINDING_RESPONSE ) then - local resp = Response.Bind.parse(hdr_data .. data) - return true, resp - end + local status, data = self.socket:receive_buf(match.numbytes(header.length), false) + if ( header.type == MessageType.BINDING_RESPONSE ) then + local resp = Response.Bind.parse(hdr_data .. data) + return true, resp + end - return false, "Unknown response message received" - end, + return false, "Unknown response message received" + end, - -- sends the request instance to the server and receives the response - -- @param req request class instance - -- @return status true on success, false on failure - -- @return response containing a response instance - -- err string containing an error message, if status is false - exch = function(self, req) - local status, err = self:send(tostring(req)) - if ( not(status) ) then - return false, "Failed to send request to server" - end - return self:recv() - end, + -- sends the request instance to the server and receives the response + -- @param req request class instance + -- @return status true on success, false on failure + -- @return response containing a response instance + -- err string containing an error message, if status is false + exch = function(self, req) + local status, err = self:send(tostring(req)) + if ( not(status) ) then + return false, "Failed to send request to server" + end + return self:recv() + end, - -- closes the connection to the server - -- @return status true on success, false on failure - -- @return err string containing an error message, if status is false - close = function(self) - self.socket:close() - end, + -- closes the connection to the server + -- @return status true on success, false on failure + -- @return err string containing an error message, if status is false + close = function(self) + self.socket:close() + end, } -- The Util class Util = { - -- creates a random string - -- @param len number containg the length of the generated random string - -- @return str containing the random string - randomString = function(len) - local str = "" - for i=1, len do str = str .. string.char(math.random(255)) end - return str - end + -- creates a random string + -- @param len number containg the length of the generated random string + -- @return str containing the random string + randomString = function(len) + local str = "" + for i=1, len do str = str .. string.char(math.random(255)) end + return str + end } -- The Helper class Helper = { - -- creates a new Helper instance - -- @param host table - -- @param port table - -- @param options table, currently supporting: - -- timeout - socket timeout in ms. - -- @param mode containing the mode container, currently Classic is the only - -- supported container - -- @return o new instance of Comm - new = function(self, host, port, options, mode) - local o = { - mode = mode, - comm = Comm:new(host, port, options, mode), - } - o.mode = stdnse.get_script_args("stun.mode") or "modern" - assert(o.mode == "modern" or o.mode == "classic", "Unsupported mode") - setmetatable(o, self) - self.__index = self - return o - end, + -- creates a new Helper instance + -- @param host table + -- @param port table + -- @param options table, currently supporting: + -- timeout - socket timeout in ms. + -- @param mode containing the mode container, currently Classic is the only + -- supported container + -- @return o new instance of Comm + new = function(self, host, port, options, mode) + local o = { + mode = mode, + comm = Comm:new(host, port, options, mode), + } + o.mode = stdnse.get_script_args("stun.mode") or "modern" + assert(o.mode == "modern" or o.mode == "classic", "Unsupported mode") + setmetatable(o, self) + self.__index = self + return o + end, - -- connects to the server - -- @return status true on success, false on failure - -- @return err string containing an error message, if status is false - connect = function(self) - return self.comm:connect() - end, + -- connects to the server + -- @return status true on success, false on failure + -- @return err string containing an error message, if status is false + connect = function(self) + return self.comm:connect() + end, - -- Get's the external public IP - -- @return status true on success, false on failure - -- @return result containing the IP as tring - getExternalAddress = function(self) - local trans_id + -- Get's the external public IP + -- @return status true on success, false on failure + -- @return result containing the IP as tring + getExternalAddress = function(self) + local trans_id - if ( self.mode == "classic" ) then - trans_id = Util.randomString(16) - else - trans_id = bin.pack("HA","2112A442", Util.randomString(12)) - end - local req = Request.Bind:new(trans_id) + if ( self.mode == "classic" ) then + trans_id = Util.randomString(16) + else + trans_id = bin.pack("HA","2112A442", Util.randomString(12)) + end + local req = Request.Bind:new(trans_id) - local status, response = self.comm:exch(req) - if ( not(status) ) then - return false, "Failed to send data to server" - end + local status, response = self.comm:exch(req) + if ( not(status) ) then + return false, "Failed to send data to server" + end - local result - for k, attr in pairs(response.attributes) do - if (attr.type == Attribute.MAPPED_ADDRESS ) then - result = ( attr.addr and attr.addr.ip or "" ) - end - if ( attr.type == Attribute.SERVER ) then - self.cache = self.cache or {} - self.cache.server = attr.server - end - end + local result + for k, attr in pairs(response.attributes) do + if (attr.type == Attribute.MAPPED_ADDRESS ) then + result = ( attr.addr and attr.addr.ip or "" ) + end + if ( attr.type == Attribute.SERVER ) then + self.cache = self.cache or {} + self.cache.server = attr.server + end + end - if ( not(result) and not(self.cache) ) then - return false, "Server returned no response" - end + if ( not(result) and not(self.cache) ) then + return false, "Server returned no response" + end - return status, result - end, + return status, result + end, - -- Gets the server version if it was returned by the server - -- @return status true on success, false on failure - -- @return version string containing the server product and version - getVersion = function(self) - local status, response = false, nil - -- check if the server version was cached - if ( not(self.cache) or not(self.cache.version) ) then - local status, response = self:getExternalAddress() - if ( status ) then - return true, (self.cache and self.cache.server or "") - end - return false, response - end - return true, (self.cache and self.cache.server or "") - end, + -- Gets the server version if it was returned by the server + -- @return status true on success, false on failure + -- @return version string containing the server product and version + getVersion = function(self) + local status, response = false, nil + -- check if the server version was cached + if ( not(self.cache) or not(self.cache.version) ) then + local status, response = self:getExternalAddress() + if ( status ) then + return true, (self.cache and self.cache.server or "") + end + return false, response + end + return true, (self.cache and self.cache.server or "") + end, - -- closes the connection to the server - -- @return status true on success, false on failure - -- @return err string containing an error message, if status is false - close = function(self) - return self.comm:close() - end, + -- closes the connection to the server + -- @return status true on success, false on failure + -- @return err string containing an error message, if status is false + close = function(self) + return self.comm:close() + end, } diff --git a/nselib/tab.lua b/nselib/tab.lua index 8e376abbc..a008e1013 100644 --- a/nselib/tab.lua +++ b/nselib/tab.lua @@ -33,11 +33,11 @@ _ENV = stdnse.module("tab", stdnse.seeall) --- Create and return a new table. -- @return A new table. function new() - local t = {} + local t = {} - t.current_row = 1 - setmetatable(t, {__tostring=dump}) - return t + t.current_row = 1 + setmetatable(t, {__tostring=dump}) + return t end --- Add a new string item to a table at a given column position. @@ -48,14 +48,14 @@ end -- @param v The string to add. -- @param c The column position at which to add the item. function add(t, c, v) - assert(t) - assert(type(v) == "string") + assert(t) + assert(type(v) == "string") - -- add a new row if one doesn't exist - t[t.current_row] = t[t.current_row] or {} + -- add a new row if one doesn't exist + t[t.current_row] = t[t.current_row] or {} - t[t.current_row][c] = v - return true + t[t.current_row][c] = v + return true end --- Add a complete row to the table and move on to the next row. @@ -65,10 +65,10 @@ end -- @param t The table. -- @param ... The elements to add to the row. function addrow(t, ...) - for i = 1, select("#", ...) do - add(t, i, tostring((select(i, ...)))) - end - nextrow(t) + for i = 1, select("#", ...) do + add(t, i, tostring((select(i, ...)))) + end + nextrow(t) end --- Move on to the next row in the table. If this is not called @@ -76,10 +76,10 @@ end -- values. -- @param t The table. function nextrow(t) - assert(t) - assert(t.current_row) - t[t.current_row] = t[t.current_row] or {} - t.current_row = t.current_row + 1 + assert(t) + assert(t.current_row) + t[t.current_row] = t[t.current_row] or {} + t.current_row = t.current_row + 1 end --- Return a formatted string representation of the table. @@ -88,41 +88,41 @@ end -- column with an additional two spaces for padding. -- @param t The table. function dump(t) - assert(t) + assert(t) - local column_width = {} - local num_columns = {} - local buf = strbuf.new() + local column_width = {} + local num_columns = {} + local buf = strbuf.new() - -- find widest element in each column - for i, row in ipairs(t) do - num_columns[i] = 0 - for x, elem in pairs(row) do - local elem_width = #elem - if not column_width[x] or elem_width > column_width[x] then - column_width[x] = elem_width - end - if x > num_columns[i] then - num_columns[i] = x - end - end - end + -- find widest element in each column + for i, row in ipairs(t) do + num_columns[i] = 0 + for x, elem in pairs(row) do + local elem_width = #elem + if not column_width[x] or elem_width > column_width[x] then + column_width[x] = elem_width + end + if x > num_columns[i] then + num_columns[i] = x + end + end + end - -- build buf with padding so all column elements line up - for i, row in ipairs(t) do - local text_row = {} - for x = 1, num_columns[i] do - local elem = row[x] or "" - if x < num_columns[i] then - text_row[#text_row + 1] = elem .. string.rep(" ", column_width[x] - #elem) - else - text_row[#text_row + 1] = elem - end - end - buf = buf .. table.concat(text_row, " ") .. "\n" - end + -- build buf with padding so all column elements line up + for i, row in ipairs(t) do + local text_row = {} + for x = 1, num_columns[i] do + local elem = row[x] or "" + if x < num_columns[i] then + text_row[#text_row + 1] = elem .. string.rep(" ", column_width[x] - #elem) + else + text_row[#text_row + 1] = elem + end + end + buf = buf .. table.concat(text_row, " ") .. "\n" + end - return strbuf.dump(buf) + return strbuf.dump(buf) end return _ENV; diff --git a/nselib/tftp.lua b/nselib/tftp.lua index 709ab74b6..d6ad79ea9 100644 --- a/nselib/tftp.lua +++ b/nselib/tftp.lua @@ -41,11 +41,11 @@ srvthread = {} -- All opcodes supported by TFTP OpCode = { - RRQ = 1, - WRQ = 2, - DATA = 3, - ACK = 4, - ERROR = 5, + RRQ = 1, + WRQ = 2, + DATA = 3, + ACK = 4, + ERROR = 5, } @@ -55,110 +55,110 @@ OpCode = { -- As the server is write-only the other packet types are not needed Packet = { - -- Implements the ACK packet - ACK = { + -- Implements the ACK packet + ACK = { - new = function( self, block ) - local o = {} - setmetatable(o, self) - self.__index = self - o.block = block - return o - end, + new = function( self, block ) + local o = {} + setmetatable(o, self) + self.__index = self + o.block = block + return o + end, - __tostring = function( self ) - return bin.pack(">SS", OpCode.ACK, self.block) - end, + __tostring = function( self ) + return bin.pack(">SS", OpCode.ACK, self.block) + end, - }, + }, - -- Implements the error packet - ERROR = { + -- Implements the error packet + ERROR = { - new = function( self, code, msg ) - local o = {} - setmetatable(o, self) - self.__index = self - o.msg = msg - o.code = code - return o - end, + new = function( self, code, msg ) + local o = {} + setmetatable(o, self) + self.__index = self + o.msg = msg + o.code = code + return o + end, - __tostring = function( self ) - return bin.pack(">SSz", OpCode.ERROR, self.code, self.msg) - end, - } + __tostring = function( self ) + return bin.pack(">SSz", OpCode.ERROR, self.code, self.msg) + end, + } } --- The File class holds files received by the TFTP server File = { - --- Creates a new file object - -- - -- @param filename string containing the filename - -- @param content string containing the file content - -- @return o new class instance - new = function(self, filename, content, sender) - local o = {} - setmetatable(o, self) - self.__index = self - o.name = filename - o.content = content - o.sender = sender - return o - end, + --- Creates a new file object + -- + -- @param filename string containing the filename + -- @param content string containing the file content + -- @return o new class instance + new = function(self, filename, content, sender) + local o = {} + setmetatable(o, self) + self.__index = self + o.name = filename + o.content = content + o.sender = sender + return o + end, - getContent = function(self) return self.content end, - setContent = function(self, content) self.content = content end, + getContent = function(self) return self.content end, + setContent = function(self, content) self.content = content end, - getName = function(self) return self.name end, - setName = function(self, name) self.name = name end, + getName = function(self) return self.name end, + setName = function(self, name) self.name = name end, - setSender = function(self, sender) self.sender = sender end, - getSender = function(self) return self.sender end, + setSender = function(self, sender) self.sender = sender end, + getSender = function(self) return self.sender end, } -- The thread dispatcher is called by the start function once local function dispatcher() - local last = os.time() - local f_condvar = nmap.condvar(infiles) - local s_condvar = nmap.condvar(state) + local last = os.time() + local f_condvar = nmap.condvar(infiles) + local s_condvar = nmap.condvar(state) - while(true) do + while(true) do - -- check if other scripts are active - local counter = 0 - for t in pairs(running) do - counter = counter + 1 - end - if ( counter == 0 ) then - state = "STOPPING" - s_condvar "broadcast" - end + -- check if other scripts are active + local counter = 0 + for t in pairs(running) do + counter = counter + 1 + end + if ( counter == 0 ) then + state = "STOPPING" + s_condvar "broadcast" + end - if #threads == 0 then break end - for i, thread in ipairs(threads) do - local status, res = coroutine.resume(thread) - if ( not(res) ) then -- thread finished its task? - table.remove(threads, i) - break - end - end + if #threads == 0 then break end + for i, thread in ipairs(threads) do + local status, res = coroutine.resume(thread) + if ( not(res) ) then -- thread finished its task? + table.remove(threads, i) + break + end + end - -- Make sure to process waitFile atleast every 2 seconds - -- in case no files have arrived - if ( os.time() - last >= 2 ) then - last = os.time() - f_condvar "broadcast" - end + -- Make sure to process waitFile atleast every 2 seconds + -- in case no files have arrived + if ( os.time() - last >= 2 ) then + last = os.time() + f_condvar "broadcast" + end - end - state = "STOPPED" - s_condvar "broadcast" - stdnse.print_debug("Exiting _dispatcher") + end + state = "STOPPED" + s_condvar "broadcast" + stdnse.print_debug("Exiting _dispatcher") end -- Processes a new incoming file transfer @@ -168,149 +168,149 @@ end -- @param port containing the port of the initiating host -- @param data string containing the initial data passed to the server local function processConnection( host, port, data ) - local pos, op = bin.unpack(">S", data) - local socket = nmap.new_socket("udp") + local pos, op = bin.unpack(">S", data) + local socket = nmap.new_socket("udp") - socket:set_timeout(1000) - local status, err = socket:connect(host, port) - if ( not(status) ) then return status, err end + socket:set_timeout(1000) + local status, err = socket:connect(host, port) + if ( not(status) ) then return status, err end - socket:set_timeout(10) + socket:set_timeout(10) - -- If we get anything else than a write request, abort the connection - if ( OpCode.WRQ ~= op ) then - stdnse.print_debug("Unsupported opcode") - socket:send( tostring(Packet.ERROR:new(0, "TFTP server has write-only support"))) - end + -- If we get anything else than a write request, abort the connection + if ( OpCode.WRQ ~= op ) then + stdnse.print_debug("Unsupported opcode") + socket:send( tostring(Packet.ERROR:new(0, "TFTP server has write-only support"))) + end - local pos, filename, enctype = bin.unpack("zz", data, pos) - status, err = socket:send( tostring( Packet.ACK:new(0) ) ) + local pos, filename, enctype = bin.unpack("zz", data, pos) + status, err = socket:send( tostring( Packet.ACK:new(0) ) ) - local blocks = {} - local lastread = os.time() + local blocks = {} + local lastread = os.time() - while( true ) do - local status, pdata = socket:receive() - if ( not(status) ) then - -- if we're here and havent succesfully read a packet for 5 seconds, abort - if ( os.time() - lastread > 5 ) then - coroutine.yield(false) - else - coroutine.yield(true) - end - else - -- record last time we had a succesful read - lastread = os.time() - pos, op = bin.unpack(">S", pdata) - if ( OpCode.DATA ~= op ) then - stdnse.print_debug("Expected a data packet, terminating TFTP transfer") - end + while( true ) do + local status, pdata = socket:receive() + if ( not(status) ) then + -- if we're here and havent succesfully read a packet for 5 seconds, abort + if ( os.time() - lastread > 5 ) then + coroutine.yield(false) + else + coroutine.yield(true) + end + else + -- record last time we had a succesful read + lastread = os.time() + pos, op = bin.unpack(">S", pdata) + if ( OpCode.DATA ~= op ) then + stdnse.print_debug("Expected a data packet, terminating TFTP transfer") + end - local block, data - pos, block, data = bin.unpack(">SA" .. #pdata - 4, pdata, pos ) + local block, data + pos, block, data = bin.unpack(">SA" .. #pdata - 4, pdata, pos ) - blocks[block] = data + blocks[block] = data - -- First block was not 1 - if ( #blocks == 0 ) then - socket:send( tostring(Packet.ERROR:new(0, "Did not receive block 1"))) - break - end + -- First block was not 1 + if ( #blocks == 0 ) then + socket:send( tostring(Packet.ERROR:new(0, "Did not receive block 1"))) + break + end - -- for every fith block check that we've received the preceeding four - if ( ( #blocks % 5 ) == 0 ) then - for b = #blocks - 4, #blocks do - if ( not(blocks[b]) ) then - socket:send( tostring(Packet.ERROR:new(0, "Did not receive block " .. b))) - end - end - end + -- for every fith block check that we've received the preceeding four + if ( ( #blocks % 5 ) == 0 ) then + for b = #blocks - 4, #blocks do + if ( not(blocks[b]) ) then + socket:send( tostring(Packet.ERROR:new(0, "Did not receive block " .. b))) + end + end + end - -- Ack the data block - status, err = socket:send( tostring(Packet.ACK:new(block)) ) + -- Ack the data block + status, err = socket:send( tostring(Packet.ACK:new(block)) ) - if ( ( #blocks % 20 ) == 0 ) then - -- yield every 5th iteration so other threads may work - coroutine.yield(true) - end + if ( ( #blocks % 20 ) == 0 ) then + -- yield every 5th iteration so other threads may work + coroutine.yield(true) + end - -- If the data length was less than 512, this was our last block - if ( #data < 512 ) then - socket:close() - break - end - end - end + -- If the data length was less than 512, this was our last block + if ( #data < 512 ) then + socket:close() + break + end + end + end - local filecontent = "" + local filecontent = "" - -- Make sure we received all the blocks needed to proceed - for i=1, #blocks do - if ( not(blocks[i]) ) then - return false, ("Block #%d was missing in transfer") - end - filecontent = filecontent .. blocks[i] - end - stdnse.print_debug("Finnished receiving file \"%s\"", filename) + -- Make sure we received all the blocks needed to proceed + for i=1, #blocks do + if ( not(blocks[i]) ) then + return false, ("Block #%d was missing in transfer") + end + filecontent = filecontent .. blocks[i] + end + stdnse.print_debug("Finnished receiving file \"%s\"", filename) - -- Add anew file to the global infiles table - table.insert( infiles, File:new(filename, filecontent, host) ) + -- Add anew file to the global infiles table + table.insert( infiles, File:new(filename, filecontent, host) ) - local condvar = nmap.condvar(infiles) - condvar "broadcast" + local condvar = nmap.condvar(infiles) + condvar "broadcast" end -- Waits for a connection from a client local function waitForConnection() - local srvsock = nmap.new_socket("udp") - local status = srvsock:bind(nil, 69) - assert(status, "Failed to bind to TFTP server port") + local srvsock = nmap.new_socket("udp") + local status = srvsock:bind(nil, 69) + assert(status, "Failed to bind to TFTP server port") - srvsock:set_timeout(0) + srvsock:set_timeout(0) - while( state == "RUNNING" ) do - local status, data = srvsock:receive() - if ( not(status) ) then - coroutine.yield(true) - else - local status, _, _, rhost, rport = srvsock:get_info() - local x = coroutine.create( function() processConnection(rhost, rport, data) end ) - table.insert( threads, x ) - coroutine.yield(true) - end - end + while( state == "RUNNING" ) do + local status, data = srvsock:receive() + if ( not(status) ) then + coroutine.yield(true) + else + local status, _, _, rhost, rport = srvsock:get_info() + local x = coroutine.create( function() processConnection(rhost, rport, data) end ) + table.insert( threads, x ) + coroutine.yield(true) + end + end end --- Starts the TFTP server and creates a new thread handing over to the dispatcher function start() - local disp = nil - local mutex = nmap.mutex("srvsocket") + local disp = nil + local mutex = nmap.mutex("srvsocket") - -- register a running script - running[coroutine.running()] = true + -- register a running script + running[coroutine.running()] = true - mutex "lock" - if ( state == "STOPPED" ) then - srvthread = coroutine.running() - table.insert( threads, coroutine.create( waitForConnection ) ) - stdnse.new_thread( dispatcher ) - state = "RUNNING" - end - mutex "done" + mutex "lock" + if ( state == "STOPPED" ) then + srvthread = coroutine.running() + table.insert( threads, coroutine.create( waitForConnection ) ) + stdnse.new_thread( dispatcher ) + state = "RUNNING" + end + mutex "done" end local function waitLast() - -- The thread that started the server needs to wait here until the rest - -- of the scripts finnish running. We know we are done once the state - -- shifts to STOPPED and we get a singla from the condvar in the - -- dispatcher - local s_condvar = nmap.condvar(state) - while( srvthread == coroutine.running() and state ~= "STOPPED" ) do - s_condvar "wait" - end + -- The thread that started the server needs to wait here until the rest + -- of the scripts finnish running. We know we are done once the state + -- shifts to STOPPED and we get a singla from the condvar in the + -- dispatcher + local s_condvar = nmap.condvar(state) + while( srvthread == coroutine.running() and state ~= "STOPPED" ) do + s_condvar "wait" + end end --- Waits for a file with a specific filename for at least the number of @@ -324,23 +324,23 @@ end -- @return status true on success false on failure -- @return File instance on success, nil on failure function waitFile( filename, timeout ) - local condvar = nmap.condvar(infiles) - local t = os.time() - while(os.time() - t < timeout) do - for _, f in ipairs(infiles) do - if (f:getName() == filename) then - running[coroutine.running()] = nil - waitLast() - return true, f - end - end - condvar "wait" - end - -- de-register a running script - running[coroutine.running()] = nil - waitLast() + local condvar = nmap.condvar(infiles) + local t = os.time() + while(os.time() - t < timeout) do + for _, f in ipairs(infiles) do + if (f:getName() == filename) then + running[coroutine.running()] = nil + waitLast() + return true, f + end + end + condvar "wait" + end + -- de-register a running script + running[coroutine.running()] = nil + waitLast() - return false + return false end return _ENV; diff --git a/nselib/unpwdb.lua b/nselib/unpwdb.lua index b9fdd7c6e..d39813d66 100644 --- a/nselib/unpwdb.lua +++ b/nselib/unpwdb.lua @@ -80,58 +80,58 @@ local customdata = false local args = nmap.registry.args local userfile = function() - if args.userdb then - customdata = true - return args.userdb - end + if args.userdb then + customdata = true + return args.userdb + end - return nmap.fetchfile("nselib/data/usernames.lst") + return nmap.fetchfile("nselib/data/usernames.lst") end local passfile = function() - if args.passdb then - customdata = true - return args.passdb - end + if args.passdb then + customdata = true + return args.passdb + end - return nmap.fetchfile("nselib/data/passwords.lst") + return nmap.fetchfile("nselib/data/passwords.lst") end local filltable = function(filename, table) - if #table ~= 0 then - return true - end + if #table ~= 0 then + return true + end - local file = io.open(filename, "r") + local file = io.open(filename, "r") - if not file then - return false - end + if not file then + return false + end - for l in file:lines() do - -- Comments takes up a whole line - if not l:match("#!comment:") then - table[#table + 1] = l - end - end + for l in file:lines() do + -- Comments takes up a whole line + if not l:match("#!comment:") then + table[#table + 1] = l + end + end - file:close() + file:close() - return true + return true end table_iterator = function(table) - local i = 1 + local i = 1 - return function(cmd) - if cmd == "reset" then - i = 1 - return - end - local elem = table[i] - if elem then i = i + 1 end - return elem - end + return function(cmd) + if cmd == "reset" then + i = 1 + return + end + local elem = table[i] + if elem then i = i + 1 end + return elem + end end --- Returns the suggested number of seconds to attempt a brute force attack, @@ -145,30 +145,30 @@ end -- still check for nil return values on the above two functions in -- case you finish before the time limit is up. timelimit = function() - -- If we're reading from a user-defined username or password list, - -- we'll give them a timeout 1.5x the default. If the "notimelimit" - -- script argument is used, we return nil. - local t = nmap.timing_level() + -- If we're reading from a user-defined username or password list, + -- we'll give them a timeout 1.5x the default. If the "notimelimit" + -- script argument is used, we return nil. + local t = nmap.timing_level() - -- Easy enough - if args.notimelimit then - return nil - end - if args["unpwdb.timelimit"] then - local limit, err = stdnse.parse_timespec(args["unpwdb.timelimit"]) - if not limit then - error(err) - end - return limit - end + -- Easy enough + if args.notimelimit then + return nil + end + if args["unpwdb.timelimit"] then + local limit, err = stdnse.parse_timespec(args["unpwdb.timelimit"]) + if not limit then + error(err) + end + return limit + end - if t <= 3 then - return (customdata and 900) or 600 - elseif t == 4 then - return (customdata and 450) or 300 - elseif t == 5 then - return (customdata and 270) or 180 - end + if t <= 3 then + return (customdata and 900) or 600 + elseif t == 4 then + return (customdata and 450) or 300 + elseif t == 5 then + return (customdata and 270) or 180 + end end --- Returns a function closure which returns a new username with every call @@ -177,17 +177,17 @@ end -- @return boolean Status. -- @return function The usernames iterator. local usernames_raw = function() - local path = userfile() + local path = userfile() - if not path then - return false, "Cannot find username list" - end + if not path then + return false, "Cannot find username list" + end - if not filltable(path, usertable) then - return false, "Error parsing username list" - end + if not filltable(path, usertable) then + return false, "Error parsing username list" + end - return true, table_iterator(usertable) + return true, table_iterator(usertable) end --- Returns a function closure which returns a new password with every call @@ -196,17 +196,17 @@ end -- @return boolean Status. -- @return function The passwords iterator. local passwords_raw = function() - local path = passfile() + local path = passfile() - if not path then - return false, "Cannot find password list" - end + if not path then + return false, "Cannot find password list" + end - if not filltable(path, passtable) then - return false, "Error parsing password list" - end + if not filltable(path, passtable) then + return false, "Error parsing password list" + end - return true, table_iterator(passtable) + return true, table_iterator(passtable) end --- Wraps time and count limits around an iterator. When either limit expires, @@ -217,27 +217,27 @@ end -- @return boolean Status. -- @return function The wrapped iterator. limited_iterator = function(iterator, time_limit, count_limit) - local start, count, elem + local start, count, elem - time_limit = (time_limit and time_limit > 0) and time_limit - count_limit = (count_limit and count_limit > 0) and count_limit + time_limit = (time_limit and time_limit > 0) and time_limit + count_limit = (count_limit and count_limit > 0) and count_limit - start = os.time() - count = 0 - return function(cmd) - if cmd == "reset" then - count = 0 - else - count = count + 1 - end - if count_limit and count > count_limit then - return - end - if time_limit and os.time() - start >= time_limit then - return - end - return iterator(cmd) - end + start = os.time() + count = 0 + return function(cmd) + if cmd == "reset" then + count = 0 + else + count = count + 1 + end + if count_limit and count > count_limit then + return + end + if time_limit and os.time() - start >= time_limit then + return + end + return iterator(cmd) + end end --- Returns a function closure which returns a new password with every call @@ -248,19 +248,19 @@ end -- @return boolean Status. -- @return function The usernames iterator. usernames = function(time_limit, count_limit) - local status, iterator + local status, iterator - status, iterator = usernames_raw() - if not status then - return false, iterator - end + status, iterator = usernames_raw() + if not status then + return false, iterator + end - time_limit = time_limit or timelimit() - if not count_limit and args["unpwdb.userlimit"] then - count_limit = tonumber(args["unpwdb.userlimit"]) - end + time_limit = time_limit or timelimit() + if not count_limit and args["unpwdb.userlimit"] then + count_limit = tonumber(args["unpwdb.userlimit"]) + end - return true, limited_iterator(iterator, time_limit, count_limit) + return true, limited_iterator(iterator, time_limit, count_limit) end --- Returns a function closure which returns a new password with every call @@ -271,19 +271,19 @@ end -- @return boolean Status. -- @return function The passwords iterator. passwords = function(time_limit, count_limit) - local status, iterator + local status, iterator - status, iterator = passwords_raw() - if not status then - return false, iterator - end + status, iterator = passwords_raw() + if not status then + return false, iterator + end - time_limit = time_limit or timelimit() - if not count_limit and args["unpwdb.passlimit"] then - count_limit = tonumber(args["unpwdb.passlimit"]) - end + time_limit = time_limit or timelimit() + if not count_limit and args["unpwdb.passlimit"] then + count_limit = tonumber(args["unpwdb.passlimit"]) + end - return true, limited_iterator(iterator, time_limit, count_limit) + return true, limited_iterator(iterator, time_limit, count_limit) end --- Returns a new iterator that iterates trough it's consecutive iterators, @@ -308,7 +308,7 @@ function concat_iterators (iter1, iter2) end end return iterator - end +end --- Returns a new iterator that filters it's results based on the filter. -- @param iterator Iterator that needs to be filtered @@ -324,6 +324,6 @@ function filter_iterator (iterator, filter) return helper(iterator(command)) end return filter - end +end return _ENV; diff --git a/nselib/upnp.lua b/nselib/upnp.lua index 9944f6271..354dbd89e 100644 --- a/nselib/upnp.lua +++ b/nselib/upnp.lua @@ -45,302 +45,302 @@ _ENV = stdnse.module("upnp", stdnse.seeall) Util = { - --- Compare function used for sorting IP-addresses - -- - -- @param a table containing first item - -- @param b table containing second item - -- @return true if a is less than b - ipCompare = function(a, b) + --- Compare function used for sorting IP-addresses + -- + -- @param a table containing first item + -- @param b table containing second item + -- @return true if a is less than b + ipCompare = function(a, b) return ipOps.compare_ip(a, "lt", b) - end, + end, } Comm = { - --- Creates a new Comm instance - -- - -- @param host string containing the host name or ip - -- @param port number containing the port to connect to - -- @return o a new instance of Comm - new = function( self, host, port ) - local o = {} - setmetatable(o, self) - self.__index = self - o.host = host - o.port = port - o.mcast = false - return o - end, + --- Creates a new Comm instance + -- + -- @param host string containing the host name or ip + -- @param port number containing the port to connect to + -- @return o a new instance of Comm + new = function( self, host, port ) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.mcast = false + return o + end, - --- Connect to the server - -- - -- @return status true on success, false on failure - connect = function( self ) - if ( self.mcast ) then - self.socket = nmap.new_socket("udp") - self.socket:set_timeout(5000) - else - self.socket = nmap.new_socket() - self.socket:set_timeout(5000) - local status, err = self.socket:connect(self.host, self.port, "udp" ) - if ( not(status) ) then return false, err end - end + --- Connect to the server + -- + -- @return status true on success, false on failure + connect = function( self ) + if ( self.mcast ) then + self.socket = nmap.new_socket("udp") + self.socket:set_timeout(5000) + else + self.socket = nmap.new_socket() + self.socket:set_timeout(5000) + local status, err = self.socket:connect(self.host, self.port, "udp" ) + if ( not(status) ) then return false, err end + end - return true - end, + return true + end, - --- Send the UPNP discovery request to the server - -- - -- @return status true on success, false on failure - sendRequest = function( self ) - local payload = strbuf.new() + --- Send the UPNP discovery request to the server + -- + -- @return status true on success, false on failure + sendRequest = function( self ) + local payload = strbuf.new() - -- for details about the UPnP message format, see http://upnp.org/resources/documents.asp - payload = payload .. "M-SEARCH * HTTP/1.1\r\n" - payload = payload .. "Host:239.255.255.250:1900\r\n" - payload = payload .. "ST:upnp:rootdevice\r\n" - payload = payload .. "Man:\"ssdp:discover\"\r\n" - payload = payload .. "MX:3\r\n\r\n" + -- for details about the UPnP message format, see http://upnp.org/resources/documents.asp + payload = payload .. "M-SEARCH * HTTP/1.1\r\n" + payload = payload .. "Host:239.255.255.250:1900\r\n" + payload = payload .. "ST:upnp:rootdevice\r\n" + payload = payload .. "Man:\"ssdp:discover\"\r\n" + payload = payload .. "MX:3\r\n\r\n" - local status, err + local status, err - if ( self.mcast ) then - status, err = self.socket:sendto( self.host, self.port, strbuf.dump(payload) ) - else - status, err = self.socket:send( strbuf.dump(payload) ) - end + if ( self.mcast ) then + status, err = self.socket:sendto( self.host, self.port, strbuf.dump(payload) ) + else + status, err = self.socket:send( strbuf.dump(payload) ) + end - if ( not(status) ) then return false, err end + if ( not(status) ) then return false, err end - return true - end, + return true + end, - --- Receives one or multiple UPNP responses depending on whether - -- setBroadcast was enabled or not. The function returns the - -- status and a response containing: - -- * an array (table) of responses if broadcast is used - -- * a single response if broadcast is not in use - -- * an error message if status was false - -- - -- @return status true on success, false on failure - -- @return result table or string containing results or error message - -- on failure. - receiveResponse = function( self ) - local status, response - local result = {} - local host_responses = {} + --- Receives one or multiple UPNP responses depending on whether + -- setBroadcast was enabled or not. The function returns the + -- status and a response containing: + -- * an array (table) of responses if broadcast is used + -- * a single response if broadcast is not in use + -- * an error message if status was false + -- + -- @return status true on success, false on failure + -- @return result table or string containing results or error message + -- on failure. + receiveResponse = function( self ) + local status, response + local result = {} + local host_responses = {} - repeat - status, response = self.socket:receive() - if ( not(status) and #response == 0 ) then - return false, response - elseif( not(status) ) then - break - end + repeat + status, response = self.socket:receive() + if ( not(status) and #response == 0 ) then + return false, response + elseif( not(status) ) then + break + end - local status, _, _, ip, _ = self.socket:get_info() - if ( not(status) ) then - return false, "Failed to retrieve socket information" - end - if target.ALLOW_NEW_TARGETS then target.add(ip) end + local status, _, _, ip, _ = self.socket:get_info() + if ( not(status) ) then + return false, "Failed to retrieve socket information" + end + if target.ALLOW_NEW_TARGETS then target.add(ip) end - if ( not(host_responses[ip]) ) then - local status, output = self:decodeResponse( response ) - if ( not(status) ) then - return false, "Failed to decode UPNP response" - end - output = { output } - output.name = ip - table.insert( result, output ) - host_responses[ip] = true - end - until ( not( self.mcast ) ) + if ( not(host_responses[ip]) ) then + local status, output = self:decodeResponse( response ) + if ( not(status) ) then + return false, "Failed to decode UPNP response" + end + output = { output } + output.name = ip + table.insert( result, output ) + host_responses[ip] = true + end + until ( not( self.mcast ) ) - if ( self.mcast ) then - table.sort(result, Util.ipCompare) - return true, result - end + if ( self.mcast ) then + table.sort(result, Util.ipCompare) + return true, result + end - if ( status and #result > 0 ) then - return true, result[1] - else - return false, "Received no responses" - end - end, + if ( status and #result > 0 ) then + return true, result[1] + else + return false, "Received no responses" + end + end, - --- Processes a response from a upnp device - -- - -- @param response as received over the socket - -- @return status boolean true on success, false on failure - -- @return response table or string suitable for output or error message if status is false - decodeResponse = function( self, response ) - local output = {} + --- Processes a response from a upnp device + -- + -- @param response as received over the socket + -- @return status boolean true on success, false on failure + -- @return response table or string suitable for output or error message if status is false + decodeResponse = function( self, response ) + local output = {} - if response ~= nil then - -- We should get a response back that has contains one line for the server, and one line for the xml file location - -- these match any combination of upper and lower case responses - local server, location - server = string.match(response, "[Ss][Ee][Rr][Vv][Ee][Rr]:%s*(.-)\r?\n") - if server ~= nil then table.insert(output, "Server: " .. server ) end - location = string.match(response, "[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s*(.-)\r?\n") - if location ~= nil then - table.insert(output, "Location: " .. location ) + if response ~= nil then + -- We should get a response back that has contains one line for the server, and one line for the xml file location + -- these match any combination of upper and lower case responses + local server, location + server = string.match(response, "[Ss][Ee][Rr][Vv][Ee][Rr]:%s*(.-)\r?\n") + if server ~= nil then table.insert(output, "Server: " .. server ) end + location = string.match(response, "[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:%s*(.-)\r?\n") + if location ~= nil then + table.insert(output, "Location: " .. location ) - local v = nmap.verbosity() + local v = nmap.verbosity() - -- the following check can output quite a lot of information, so we require at least one -v flag - if v > 0 then - local status, result = self:retrieveXML( location ) - if status then - table.insert(output, result) - end - end - end - if #output > 0 then - return true, output - else - return false, "Could not decode response" - end - end - end, + -- the following check can output quite a lot of information, so we require at least one -v flag + if v > 0 then + local status, result = self:retrieveXML( location ) + if status then + table.insert(output, result) + end + end + end + if #output > 0 then + return true, output + else + return false, "Could not decode response" + end + end + end, - --- Retrieves the XML file that describes the UPNP device - -- - -- @param location string containing the location of the XML file from the UPNP response - -- @return status boolean true on success, false on failure - -- @return response table or string suitable for output or error message if status is false - retrieveXML = function( self, location ) - local response - local options = {} - options['header'] = {} - options['header']['Accept'] = "text/xml, application/xml, text/html" + --- Retrieves the XML file that describes the UPNP device + -- + -- @param location string containing the location of the XML file from the UPNP response + -- @return status boolean true on success, false on failure + -- @return response table or string suitable for output or error message if status is false + retrieveXML = function( self, location ) + local response + local options = {} + options['header'] = {} + options['header']['Accept'] = "text/xml, application/xml, text/html" - -- if we're in multicast mode, or if the user doesn't want us to override the IP address, - -- just use the HTTP library to grab the XML file - if ( self.mcast or ( not self.override ) ) then - response = http.get_url( location, options ) - else - -- otherwise, split the location into an IP address, port, and path name for the xml file - local xhost, xport, xfile - xhost = string.match(location, "http://(.-)/") - -- check to see if the host portion of the location specifies a port - -- if not, use port 80 as a standard web server port - if xhost ~= nil and string.match(xhost, ":") then - xport = string.match(xhost, ":(.*)") - xhost = string.match(xhost, "(.*):") - end + -- if we're in multicast mode, or if the user doesn't want us to override the IP address, + -- just use the HTTP library to grab the XML file + if ( self.mcast or ( not self.override ) ) then + response = http.get_url( location, options ) + else + -- otherwise, split the location into an IP address, port, and path name for the xml file + local xhost, xport, xfile + xhost = string.match(location, "http://(.-)/") + -- check to see if the host portion of the location specifies a port + -- if not, use port 80 as a standard web server port + if xhost ~= nil and string.match(xhost, ":") then + xport = string.match(xhost, ":(.*)") + xhost = string.match(xhost, "(.*):") + end - -- check to see if the IP address returned matches the IP address we scanned - if xhost ~= self.host.ip then - stdnse.print_debug("IP addresses did not match! Found %s, using %s instead.", xhost, self.host.ip) - xhost = self.host.ip - end + -- check to see if the IP address returned matches the IP address we scanned + if xhost ~= self.host.ip then + stdnse.print_debug("IP addresses did not match! Found %s, using %s instead.", xhost, self.host.ip) + xhost = self.host.ip + end - if xport == nil then - xport = 80 - end + if xport == nil then + xport = 80 + end - -- extract the path name from the location field, but strip off the \r that HTTP servers return - xfile = string.match(location, "http://.-(/.-)\013") - if xfile ~= nil then - response = http.get( xhost, xport, xfile, options ) - end - end + -- extract the path name from the location field, but strip off the \r that HTTP servers return + xfile = string.match(location, "http://.-(/.-)\013") + if xfile ~= nil then + response = http.get( xhost, xport, xfile, options ) + end + end - if response ~= nil then - local output = {} + if response ~= nil then + local output = {} - -- extract information about the webserver that is handling responses for the UPnP system - local webserver = response['header']['server'] - if webserver ~= nil then table.insert(output, "Webserver: " .. webserver) end + -- extract information about the webserver that is handling responses for the UPnP system + local webserver = response['header']['server'] + if webserver ~= nil then table.insert(output, "Webserver: " .. webserver) end - -- the schema for UPnP includes a number of entries, which can a number of interesting fields - for device in string.gmatch(response['body'], "(.-)") do - local fn, mnf, mdl, nm, ver + -- the schema for UPnP includes a number of entries, which can a number of interesting fields + for device in string.gmatch(response['body'], "(.-)") do + local fn, mnf, mdl, nm, ver - fn = string.match(device, "(.-)") - mnf = string.match(device, "(.-)") - mdl = string.match(device, "(.-)") - nm = string.match(device, "(.-)") - ver = string.match(device, "(.-)") + fn = string.match(device, "(.-)") + mnf = string.match(device, "(.-)") + mdl = string.match(device, "(.-)") + nm = string.match(device, "(.-)") + ver = string.match(device, "(.-)") - if fn ~= nil then table.insert(output, "Name: " .. fn) end - if mnf ~= nil then table.insert(output,"Manufacturer: " .. mnf) end - if mdl ~= nil then table.insert(output,"Model Descr: " .. mdl) end - if nm ~= nil then table.insert(output,"Model Name: " .. nm) end - if ver ~= nil then table.insert(output,"Model Version: " .. ver) end - end - return true, output - else - return false, "Could not retrieve XML file" - end - end, + if fn ~= nil then table.insert(output, "Name: " .. fn) end + if mnf ~= nil then table.insert(output,"Manufacturer: " .. mnf) end + if mdl ~= nil then table.insert(output,"Model Descr: " .. mdl) end + if nm ~= nil then table.insert(output,"Model Name: " .. nm) end + if ver ~= nil then table.insert(output,"Model Version: " .. ver) end + end + return true, output + else + return false, "Could not retrieve XML file" + end + end, - --- Enables or disables multicast support - -- - -- @param mcast boolean true if multicast is to be used, false otherwise - setMulticast = function( self, mcast ) - assert( type(mcast)=="boolean", "mcast has to be either true or false") - self.mcast = mcast - local family = nmap.address_family() - self.host = (family=="inet6" and "FF02::C" or "239.255.255.250") - self.port = 1900 - end, + --- Enables or disables multicast support + -- + -- @param mcast boolean true if multicast is to be used, false otherwise + setMulticast = function( self, mcast ) + assert( type(mcast)=="boolean", "mcast has to be either true or false") + self.mcast = mcast + local family = nmap.address_family() + self.host = (family=="inet6" and "FF02::C" or "239.255.255.250") + self.port = 1900 + end, - --- Closes the socket - close = function( self ) self.socket:close() end + --- Closes the socket + close = function( self ) self.socket:close() end } Helper = { - --- Creates a new helper instance - -- - -- @param host string containing the host name or ip - -- @param port number containing the port to connect to - -- @return o a new instance of Helper - new = function( self, host, port ) - local o = {} - setmetatable(o, self) - self.__index = self - o.comm = Comm:new( host, port ) - return o - end, + --- Creates a new helper instance + -- + -- @param host string containing the host name or ip + -- @param port number containing the port to connect to + -- @return o a new instance of Helper + new = function( self, host, port ) + local o = {} + setmetatable(o, self) + self.__index = self + o.comm = Comm:new( host, port ) + return o + end, - --- Enables or disables multicast support - -- - -- @param mcast boolean true if multicast is to be used, false otherwise - setMulticast = function( self, mcast ) self.comm:setMulticast(mcast) end, + --- Enables or disables multicast support + -- + -- @param mcast boolean true if multicast is to be used, false otherwise + setMulticast = function( self, mcast ) self.comm:setMulticast(mcast) end, - --- Enables or disables whether the script will override the IP address is the Location URL - -- - -- @param override boolean true if override is to be enabled, false otherwise - setOverride = function( self, override ) - assert( type(override)=="boolean", "override has to be either true or false") - self.comm.override = override - end, + --- Enables or disables whether the script will override the IP address is the Location URL + -- + -- @param override boolean true if override is to be enabled, false otherwise + setOverride = function( self, override ) + assert( type(override)=="boolean", "override has to be either true or false") + self.comm.override = override + end, - --- Sends a UPnP queries and collects a single or multiple responses - -- - -- @return status true on success, false on failure - -- @return result table or string containing results or error message - -- on failure. - queryServices = function( self ) - local status, err = self.comm:connect() - local response + --- Sends a UPnP queries and collects a single or multiple responses + -- + -- @return status true on success, false on failure + -- @return result table or string containing results or error message + -- on failure. + queryServices = function( self ) + local status, err = self.comm:connect() + local response - if ( not(status) ) then return false, err end + if ( not(status) ) then return false, err end - status, err = self.comm:sendRequest() - if ( not(status) ) then return false, err end + status, err = self.comm:sendRequest() + if ( not(status) ) then return false, err end - status, response = self.comm:receiveResponse() - self.comm:close() + status, response = self.comm:receiveResponse() + self.comm:close() - return status, response - end, + return status, response + end, } diff --git a/nselib/url.lua b/nselib/url.lua index a14b0a4e0..fdb4899c8 100644 --- a/nselib/url.lua +++ b/nselib/url.lua @@ -43,21 +43,21 @@ _ENV = stdnse.module("url", stdnse.seeall) _VERSION = "URL 1.0" - --[[ Internal functions --]] +--[[ Internal functions --]] local function make_set(t) - local s = {} - for i,v in base.ipairs(t) do - s[t[i]] = 1 - end - return s + local s = {} + for i,v in base.ipairs(t) do + s[t[i]] = 1 + end + return s end -- these are allowed withing a path segment, along with alphanum -- other characters must be escaped local segment_set = make_set { - "-", "_", ".", "!", "~", "*", "'", "(", - ")", ":", "@", "&", "=", "+", "$", ",", + "-", "_", ".", "!", "~", "*", "'", "(", + ")", ":", "@", "&", "=", "+", "$", ",", } --- @@ -66,10 +66,10 @@ local segment_set = make_set { -- @param s Binary string to be encoded. -- @return Escaped representation of string. local function protect_segment(s) - return string.gsub(s, "([^A-Za-z0-9_])", function (c) - if segment_set[c] then return c - else return string.format("%%%02x", string.byte(c)) end - end) + return string.gsub(s, "([^A-Za-z0-9_])", function (c) + if segment_set[c] then return c + else return string.format("%%%02x", string.byte(c)) end + end) end --- @@ -79,28 +79,28 @@ end -- @return The corresponding absolute path. ----------------------------------------------------------------------------- local function absolute_path(base_path, relative_path) - if string.sub(relative_path, 1, 1) == "/" then return relative_path end - local path = string.gsub(base_path, "[^/]*$", "") - path = path .. relative_path - path = string.gsub(path, "([^/]*%./)", function (s) - if s ~= "./" then return s else return "" end + if string.sub(relative_path, 1, 1) == "/" then return relative_path end + local path = string.gsub(base_path, "[^/]*$", "") + path = path .. relative_path + path = string.gsub(path, "([^/]*%./)", function (s) + if s ~= "./" then return s else return "" end + end) + path = string.gsub(path, "/%.$", "/") + local reduced + while reduced ~= path do + reduced = path + path = string.gsub(reduced, "([^/]*/%.%./)", function (s) + if s ~= "../../" then return "" else return s end end) - path = string.gsub(path, "/%.$", "/") - local reduced - while reduced ~= path do - reduced = path - path = string.gsub(reduced, "([^/]*/%.%./)", function (s) - if s ~= "../../" then return "" else return s end - end) - end - path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) - if s ~= "../.." then return "" else return s end - end) - return path + end + path = string.gsub(reduced, "([^/]*/%.%.)$", function (s) + if s ~= "../.." then return "" else return s end + end) + return path end - --[[ External functions --]] +--[[ External functions --]] --- -- Encodes a string into its escaped hexadecimal representation. @@ -108,9 +108,9 @@ end -- @return Escaped representation of string. ----------------------------------------------------------------------------- function escape(s) - return string.gsub(s, "([^A-Za-z0-9_])", function(c) - return string.format("%%%02x", string.byte(c)) - end) + return string.gsub(s, "([^A-Za-z0-9_])", function(c) + return string.format("%%%02x", string.byte(c)) + end) end @@ -120,9 +120,9 @@ end -- @return Decoded string. ----------------------------------------------------------------------------- function unescape(s) - return string.gsub(s, "%%(%x%x)", function(hex) - return string.char(base.tonumber(hex, 16)) - end) + return string.gsub(s, "%%(%x%x)", function(hex) + return string.char(base.tonumber(hex, 16)) + end) end @@ -149,49 +149,49 @@ end -- query, and fragment. ----------------------------------------------------------------------------- function parse(url, default) - -- initialize default parameters - local parsed = {} - for i,v in base.pairs(default or parsed) do parsed[i] = v end - -- remove whitespace - -- url = string.gsub(url, "%s", "") - -- get fragment - url = string.gsub(url, "#(.*)$", function(f) - parsed.fragment = f - return "" - end) - -- get scheme. Lower-case according to RFC 3986 section 3.1. - url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", - function(s) parsed.scheme = string.lower(s); return "" end) - -- get authority - url = string.gsub(url, "^//([^/]*)", function(n) - parsed.authority = n - return "" - end) - -- get query stringing - url = string.gsub(url, "%?(.*)", function(q) - parsed.query = q - return "" - end) - -- get params - url = string.gsub(url, "%;(.*)", function(p) - parsed.params = p - return "" - end) - -- path is whatever was left - parsed.path = url - local authority = parsed.authority - if not authority then return parsed end - authority = string.gsub(authority,"^([^@]*)@", - function(u) parsed.userinfo = u; return "" end) - authority = string.gsub(authority, ":([0-9]*)$", - function(p) if p ~= "" then parsed.port = p end; return "" end) - if authority ~= "" then parsed.host = authority end - local userinfo = parsed.userinfo - if not userinfo then return parsed end - userinfo = string.gsub(userinfo, ":([^:]*)$", - function(p) parsed.password = p; return "" end) - parsed.user = userinfo - return parsed + -- initialize default parameters + local parsed = {} + for i,v in base.pairs(default or parsed) do parsed[i] = v end + -- remove whitespace + -- url = string.gsub(url, "%s", "") + -- get fragment + url = string.gsub(url, "#(.*)$", function(f) + parsed.fragment = f + return "" + end) + -- get scheme. Lower-case according to RFC 3986 section 3.1. + url = string.gsub(url, "^([%w][%w%+%-%.]*)%:", + function(s) parsed.scheme = string.lower(s); return "" end) + -- get authority + url = string.gsub(url, "^//([^/]*)", function(n) + parsed.authority = n + return "" + end) + -- get query stringing + url = string.gsub(url, "%?(.*)", function(q) + parsed.query = q + return "" + end) + -- get params + url = string.gsub(url, "%;(.*)", function(p) + parsed.params = p + return "" + end) + -- path is whatever was left + parsed.path = url + local authority = parsed.authority + if not authority then return parsed end + authority = string.gsub(authority,"^([^@]*)@", + function(u) parsed.userinfo = u; return "" end) + authority = string.gsub(authority, ":([0-9]*)$", + function(p) if p ~= "" then parsed.port = p end; return "" end) + if authority ~= "" then parsed.host = authority end + local userinfo = parsed.userinfo + if not userinfo then return parsed end + userinfo = string.gsub(userinfo, ":([^:]*)$", + function(p) parsed.password = p; return "" end) + parsed.user = userinfo + return parsed end --- @@ -202,28 +202,28 @@ end -- @return A string with the corresponding URL. ----------------------------------------------------------------------------- function build(parsed) - local ppath = parse_path(parsed.path or "") - local url = build_path(ppath) - if parsed.params then url = url .. ";" .. parsed.params end - if parsed.query then url = url .. "?" .. parsed.query end - local authority = parsed.authority - if parsed.host then - authority = parsed.host - if parsed.port then authority = authority .. ":" .. parsed.port end - local userinfo = parsed.userinfo - if parsed.user then - userinfo = parsed.user - if parsed.password then - userinfo = userinfo .. ":" .. parsed.password - end - end - if userinfo then authority = userinfo .. "@" .. authority end - end - if authority then url = "//" .. authority .. url end - if parsed.scheme then url = parsed.scheme .. ":" .. url end - if parsed.fragment then url = url .. "#" .. parsed.fragment end - -- url = string.gsub(url, "%s", "") - return url + local ppath = parse_path(parsed.path or "") + local url = build_path(ppath) + if parsed.params then url = url .. ";" .. parsed.params end + if parsed.query then url = url .. "?" .. parsed.query end + local authority = parsed.authority + if parsed.host then + authority = parsed.host + if parsed.port then authority = authority .. ":" .. parsed.port end + local userinfo = parsed.userinfo + if parsed.user then + userinfo = parsed.user + if parsed.password then + userinfo = userinfo .. ":" .. parsed.password + end + end + if userinfo then authority = userinfo .. "@" .. authority end + end + if authority then url = "//" .. authority .. url end + if parsed.scheme then url = parsed.scheme .. ":" .. url end + if parsed.fragment then url = url .. "#" .. parsed.fragment end + -- url = string.gsub(url, "%s", "") + return url end --- @@ -233,36 +233,36 @@ end -- @return The corresponding absolute URL. ----------------------------------------------------------------------------- function absolute(base_url, relative_url) - local base_parsed; - if type(base_url) == "table" then - base_parsed = base_url - base_url = build(base_parsed) - else - base_parsed = parse(base_url) - end - local relative_parsed = parse(relative_url) - if not base_parsed then return relative_url - elseif not relative_parsed then return base_url - elseif relative_parsed.scheme then return relative_url - else - relative_parsed.scheme = base_parsed.scheme - if not relative_parsed.authority then - relative_parsed.authority = base_parsed.authority - if not relative_parsed.path then - relative_parsed.path = base_parsed.path - if not relative_parsed.params then - relative_parsed.params = base_parsed.params - if not relative_parsed.query then - relative_parsed.query = base_parsed.query - end - end - else - relative_parsed.path = absolute_path(base_parsed.path or "", - relative_parsed.path) - end + local base_parsed; + if type(base_url) == "table" then + base_parsed = base_url + base_url = build(base_parsed) + else + base_parsed = parse(base_url) + end + local relative_parsed = parse(relative_url) + if not base_parsed then return relative_url + elseif not relative_parsed then return base_url + elseif relative_parsed.scheme then return relative_url + else + relative_parsed.scheme = base_parsed.scheme + if not relative_parsed.authority then + relative_parsed.authority = base_parsed.authority + if not relative_parsed.path then + relative_parsed.path = base_parsed.path + if not relative_parsed.params then + relative_parsed.params = base_parsed.params + if not relative_parsed.query then + relative_parsed.query = base_parsed.query + end end - return build(relative_parsed) + else + relative_parsed.path = absolute_path(base_parsed.path or "", + relative_parsed.path) + end end + return build(relative_parsed) + end end --- @@ -271,16 +271,16 @@ end -- @return A table with one entry per segment. ----------------------------------------------------------------------------- function parse_path(path) - local parsed = {} - path = path or "" - --path = string.gsub(path, "%s", "") - string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) - for i, v in ipairs(parsed) do - parsed[i] = unescape(v) - end - if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end - if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end - return parsed + local parsed = {} + path = path or "" + --path = string.gsub(path, "%s", "") + string.gsub(path, "([^/]+)", function (s) table.insert(parsed, s) end) + for i, v in ipairs(parsed) do + parsed[i] = unescape(v) + end + if string.sub(path, 1, 1) == "/" then parsed.is_absolute = 1 end + if string.sub(path, -1, -1) == "/" then parsed.is_directory = 1 end + return parsed end --- @@ -290,29 +290,29 @@ end -- @return The corresponding path string ----------------------------------------------------------------------------- function build_path(parsed, unsafe) - local path = "" - local n = #parsed - if unsafe then - for i = 1, n-1 do - path = path .. parsed[i] - path = path .. "/" - end - if n > 0 then - path = path .. parsed[n] - if parsed.is_directory then path = path .. "/" end - end - else - for i = 1, n-1 do - path = path .. protect_segment(parsed[i]) - path = path .. "/" - end - if n > 0 then - path = path .. protect_segment(parsed[n]) - if parsed.is_directory then path = path .. "/" end - end - end - if parsed.is_absolute then path = "/" .. path end - return path + local path = "" + local n = #parsed + if unsafe then + for i = 1, n-1 do + path = path .. parsed[i] + path = path .. "/" + end + if n > 0 then + path = path .. parsed[n] + if parsed.is_directory then path = path .. "/" end + end + else + for i = 1, n-1 do + path = path .. protect_segment(parsed[i]) + path = path .. "/" + end + if n > 0 then + path = path .. protect_segment(parsed[n]) + if parsed.is_directory then path = path .. "/" end + end + end + if parsed.is_absolute then path = "/" .. path end + return path end --- @@ -327,31 +327,31 @@ end -- table["name"] = value. ----------------------------------------------------------------------------- function parse_query(query) - local parsed = {} - local pos = 0 + local parsed = {} + local pos = 0 - query = string.gsub(query, "&", "&") - query = string.gsub(query, "<", "<") - query = string.gsub(query, ">", ">") + query = string.gsub(query, "&", "&") + query = string.gsub(query, "<", "<") + query = string.gsub(query, ">", ">") - local function ginsert(qstr) - local first, last = string.find(qstr, "=") - if first then - parsed[string.sub(qstr, 0, first-1)] = string.sub(qstr, first+1) - end - end + local function ginsert(qstr) + local first, last = string.find(qstr, "=") + if first then + parsed[string.sub(qstr, 0, first-1)] = string.sub(qstr, first+1) + end + end - while true do - local first, last = string.find(query, "&", pos) - if first then - ginsert(string.sub(query, pos, first-1)); - pos = last+1 - else - ginsert(string.sub(query, pos)); - break; - end - end - return parsed + while true do + local first, last = string.find(query, "&", pos) + if first then + ginsert(string.sub(query, pos, first-1)); + pos = last+1 + else + ginsert(string.sub(query, pos)); + break; + end + end + return parsed end --- @@ -363,12 +363,12 @@ end -- @return A query string (like "name=value2&name=value2"). ----------------------------------------------------------------------------- function build_query(query) - local qstr = "" + local qstr = "" - for i,v in pairs(query) do - qstr = qstr .. i .. '=' .. v .. '&' - end - return string.sub(qstr, 0, #qstr-1) + for i,v in pairs(query) do + qstr = qstr .. i .. '=' .. v .. '&' + end + return string.sub(qstr, 0, #qstr-1) end return _ENV; diff --git a/nselib/versant.lua b/nselib/versant.lua index c51416038..da9580369 100644 --- a/nselib/versant.lua +++ b/nselib/versant.lua @@ -18,270 +18,270 @@ _ENV = stdnse.module("versant", stdnse.seeall) Versant = { - -- fallback to these constants when version and user are not given - USER = "nmap", - VERSION = "8.0.2", + -- fallback to these constants when version and user are not given + USER = "nmap", + VERSION = "8.0.2", - -- Creates an instance of the Versant class - -- @param host table - -- @param port table - -- @return o new instance of Versant - new = function(self, host, port) - local o = { host = host, port = port, socket = nmap.new_socket() } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates an instance of the Versant class + -- @param host table + -- @param port table + -- @return o new instance of Versant + new = function(self, host, port) + local o = { host = host, port = port, socket = nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects a socket to the Versant server - -- @return status true on success, false on failure - -- @return err string containing the error message if status is false - connect = function(self) - return self.socket:connect(self.host, self.port) - end, + -- Connects a socket to the Versant server + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + connect = function(self) + return self.socket:connect(self.host, self.port) + end, - -- Closes the socket - -- @return status true on success, false on failure - -- @return err string containing the error message if status is false - close = function(self) - return self.socket:close() - end, + -- Closes the socket + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + close = function(self) + return self.socket:close() + end, - -- Sends command to the server - -- @param cmd string containing the command to run - -- @param arg string containing any arguments - -- @param user [optional] string containing the user name - -- @param ver [optional] string containing the version number - -- @return status true on success, false on failure - -- @return data opaque string containing the response - sendCommand = function(self, cmd, arg, user, ver) + -- Sends command to the server + -- @param cmd string containing the command to run + -- @param arg string containing any arguments + -- @param user [optional] string containing the user name + -- @param ver [optional] string containing the version number + -- @return status true on success, false on failure + -- @return data opaque string containing the response + sendCommand = function(self, cmd, arg, user, ver) - user = user or Versant.USER - ver = ver or Versant.VERSION - arg = arg or "" + user = user or Versant.USER + ver = ver or Versant.VERSION + arg = arg or "" - local data = bin.pack("H", "000100000000000000020002000000010000000000000000000000000000000000010000") - data = data .. cmd .. "\0" .. user .. "\0" .. ver .. "\0" - -- align to even 4 bytes - if ( #data % 4 ~= 0 ) then - for i=1, ( 4 - (#data % 4)) do - data = data .. "\0" - end - end + local data = bin.pack("H", "000100000000000000020002000000010000000000000000000000000000000000010000") + data = data .. cmd .. "\0" .. user .. "\0" .. ver .. "\0" + -- align to even 4 bytes + if ( #data % 4 ~= 0 ) then + for i=1, ( 4 - (#data % 4)) do + data = data .. "\0" + end + end - data = data .. bin.pack("H", "0000000b000001000000000000000000") - data = data .. ("%s:%d\0"):format(self.host.ip, self.port.number) - data = data .. "\0\0\0\0\0\0\0\0\0\0" .. arg + data = data .. bin.pack("H", "0000000b000001000000000000000000") + data = data .. ("%s:%d\0"):format(self.host.ip, self.port.number) + data = data .. "\0\0\0\0\0\0\0\0\0\0" .. arg - while ( #data < 2048 ) do - data = data .. "\0" - end + while ( #data < 2048 ) do + data = data .. "\0" + end - local status, err = self.socket:send(data) - if ( not(status) ) then - return false, "Failed to send request to server" - end + local status, err = self.socket:send(data) + if ( not(status) ) then + return false, "Failed to send request to server" + end - local status, data = self.socket:receive_buf(match.numbytes(2048), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + local status, data = self.socket:receive_buf(match.numbytes(2048), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - return status, data - end, + return status, data + end, - -- Get database node information - -- @return status true on success, false on failure - -- @return result table containing an entry for each database. Each entry - -- contains a table with the following fields: - -- name - the database name - -- owner - the database owner - -- created - the date when the database was created - -- version - the database version - getNodeInfo = function(self) - local status, data = self:sendCommand("o_getnodeinfo", "-nodeinfo") - if ( not(status) ) then - return false, data - end + -- Get database node information + -- @return status true on success, false on failure + -- @return result table containing an entry for each database. Each entry + -- contains a table with the following fields: + -- name - the database name + -- owner - the database owner + -- created - the date when the database was created + -- version - the database version + getNodeInfo = function(self) + local status, data = self:sendCommand("o_getnodeinfo", "-nodeinfo") + if ( not(status) ) then + return false, data + end - status, data = self.socket:receive_buf(match.numbytes(4), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + status, data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - local _, db_count = bin.unpack(">I", data) - if ( db_count == 0 ) then - return false, "Database count was zero" - end + local _, db_count = bin.unpack(">I", data) + if ( db_count == 0 ) then + return false, "Database count was zero" + end - status, data = self.socket:receive_buf(match.numbytes(4), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + status, data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - local _, buf_size = bin.unpack(">I", data) - local dbs = {} + local _, buf_size = bin.unpack(">I", data) + local dbs = {} - for i=1, db_count do - status, data = self.socket:receive_buf(match.numbytes(buf_size), true) - local _, db = nil, {} + for i=1, db_count do + status, data = self.socket:receive_buf(match.numbytes(buf_size), true) + local _, db = nil, {} - _, db.name = bin.unpack("z", data, 23) - _, db.owner = bin.unpack("z", data, 599) - _, db.created= bin.unpack("z", data, 631) - _, db.version= bin.unpack("z", data, 663) + _, db.name = bin.unpack("z", data, 23) + _, db.owner = bin.unpack("z", data, 599) + _, db.created= bin.unpack("z", data, 631) + _, db.version= bin.unpack("z", data, 663) - -- remove trailing line-feed - db.created = db.created:match("^(.-)\n*$") + -- remove trailing line-feed + db.created = db.created:match("^(.-)\n*$") - table.insert(dbs, db) - end - return true, dbs - end, + table.insert(dbs, db) + end + return true, dbs + end, - -- Gets the database OBE port, this port is dynamically allocated once this - -- command completes. - -- - -- @return status true on success, false on failure - -- @return port table containing the OBE port - getObePort = function(self) + -- Gets the database OBE port, this port is dynamically allocated once this + -- command completes. + -- + -- @return status true on success, false on failure + -- @return port table containing the OBE port + getObePort = function(self) - local status, data = self:sendCommand("o_oscp", "-utility") - if ( not(status) ) then - return false, data - end + local status, data = self:sendCommand("o_oscp", "-utility") + if ( not(status) ) then + return false, data + end - status, data = self.socket:receive_buf(match.numbytes(256), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + status, data = self.socket:receive_buf(match.numbytes(256), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - local pos, success = bin.unpack(">I", data) - if ( success ~= 0 ) then - return false, "Response contained invalid data" - end + local pos, success = bin.unpack(">I", data) + if ( success ~= 0 ) then + return false, "Response contained invalid data" + end - local port = { protocol = "tcp" } - pos, port.number = bin.unpack(">S", data, pos) + local port = { protocol = "tcp" } + pos, port.number = bin.unpack(">S", data, pos) - return true, port - end, + return true, port + end, - -- Get's the XML license file from the database - -- @return status true on success, false on failure - -- @return data string containing the XML license file - getLicense = function(self) + -- Get's the XML license file from the database + -- @return status true on success, false on failure + -- @return data string containing the XML license file + getLicense = function(self) - local status, data = self:sendCommand("o_licfile", "-license") - if ( not(status) ) then - return false, data - end + local status, data = self:sendCommand("o_licfile", "-license") + if ( not(status) ) then + return false, data + end - status, data = self.socket:receive_buf(match.numbytes(4), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + status, data = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - local _, len = bin.unpack(">I", data) - if ( len == 0 ) then - return false, "Failed to retrieve license file" - end + local _, len = bin.unpack(">I", data) + if ( len == 0 ) then + return false, "Failed to retrieve license file" + end - status, data = self.socket:receive_buf(match.numbytes(len), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + status, data = self.socket:receive_buf(match.numbytes(len), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - return true, data - end, + return true, data + end, - -- Gets the TCP port for a given database - -- @param db string containing the database name - -- @return status true on success, false on failure - -- @return port table containing the database port - getDbPort = function(self, db) - local status, data = self:sendCommand(db, "") - if ( not(status) ) then - return false, data - end + -- Gets the TCP port for a given database + -- @param db string containing the database name + -- @return status true on success, false on failure + -- @return port table containing the database port + getDbPort = function(self, db) + local status, data = self:sendCommand(db, "") + if ( not(status) ) then + return false, data + end - if ( not(status) ) then - return false, "Failed to connect to database" - end + if ( not(status) ) then + return false, "Failed to connect to database" + end - local _, port = nil, { protocol = "tcp" } - _, port.number = bin.unpack(">I", data, 27) - if ( port == 0 ) then - return false, "Failed to determine database port" - end - return true, port - end, + local _, port = nil, { protocol = "tcp" } + _, port.number = bin.unpack(">I", data, 27) + if ( port == 0 ) then + return false, "Failed to determine database port" + end + return true, port + end, } Versant.OBE = { - -- Creates a new versant OBE instance - -- @param host table - -- @param port table - -- @return o new instance of Versant OBE - new = function(self, host, port) - local o = { host = host, port = port, socket = nmap.new_socket() } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new versant OBE instance + -- @param host table + -- @param port table + -- @return o new instance of Versant OBE + new = function(self, host, port) + local o = { host = host, port = port, socket = nmap.new_socket() } + setmetatable(o, self) + self.__index = self + return o + end, - -- Connects a socket to the Versant server - -- @return status true on success, false on failure - -- @return err string containing the error message if status is false - connect = function(self) - return self.socket:connect(self.host, self.port) - end, + -- Connects a socket to the Versant server + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + connect = function(self) + return self.socket:connect(self.host, self.port) + end, - -- Closes the socket - -- @return status true on success, false on failure - -- @return err string containing the error message if status is false - close = function(self) - return self.socket:close() - end, + -- Closes the socket + -- @return status true on success, false on failure + -- @return err string containing the error message if status is false + close = function(self) + return self.socket:close() + end, - -- Get database information including file paths and hostname - -- @return status true on success false on failure - -- @return result table containing the fields: - -- root_path - the database root directory - -- db_path - the database directory - -- lib_path - the library directory - -- hostname - the database host name - getVODInfo = function(self) - local data = bin.pack("H", "1002005d00000000000100000000000d000000000000000000000000") - data = data .. "-noprint -i " + -- Get database information including file paths and hostname + -- @return status true on success false on failure + -- @return result table containing the fields: + -- root_path - the database root directory + -- db_path - the database directory + -- lib_path - the library directory + -- hostname - the database host name + getVODInfo = function(self) + local data = bin.pack("H", "1002005d00000000000100000000000d000000000000000000000000") + data = data .. "-noprint -i " - while( #data < 256 ) do - data = data .. "\0" - end + while( #data < 256 ) do + data = data .. "\0" + end - self.socket:send(data) - local status, data = self.socket:receive_buf(match.numbytes(256), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + self.socket:send(data) + local status, data = self.socket:receive_buf(match.numbytes(256), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - local pos, len = bin.unpack(">I", data, 13) - status, data = self.socket:receive_buf(match.numbytes(len), true) - if ( not(status) ) then - return false, "Failed to read response from server" - end + local pos, len = bin.unpack(">I", data, 13) + status, data = self.socket:receive_buf(match.numbytes(len), true) + if ( not(status) ) then + return false, "Failed to read response from server" + end - local result, pos, offset = {}, 1, 13 - pos, result.version = bin.unpack("z", data) + local result, pos, offset = {}, 1, 13 + pos, result.version = bin.unpack("z", data) - for _, item in ipairs({"root_path", "db_path", "lib_path", "hostname"}) do - pos, result[item] = bin.unpack("z", data, offset) - offset = offset + 256 - end - return true, result - end, + for _, item in ipairs({"root_path", "db_path", "lib_path", "hostname"}) do + pos, result[item] = bin.unpack("z", data, offset) + offset = offset + 256 + end + return true, result + end, } return _ENV; diff --git a/nselib/vnc.lua b/nselib/vnc.lua index 3af06cb8d..f8f65061d 100644 --- a/nselib/vnc.lua +++ b/nselib/vnc.lua @@ -15,7 +15,7 @@ -- The library contains the following classes: -- -- o VNC --- - This class contains the core functions needed to communicate with VNC +-- - This class contains the core functions needed to communicate with VNC -- -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html @@ -36,263 +36,263 @@ local HAVE_SSL, openssl = pcall(require,'openssl') VNC = { - -- We currently support version 3.8 of the protocol only - versions = { - ["RFB 003.003\n"] = "3.3", - ["RFB 003.007\n"] = "3.7", - ["RFB 003.008\n"] = "3.8", + -- We currently support version 3.8 of the protocol only + versions = { + ["RFB 003.003\n"] = "3.3", + ["RFB 003.007\n"] = "3.7", + ["RFB 003.008\n"] = "3.8", - -- Mac Screen Sharing, could probably be used to fingerprint OS - ["RFB 003.889\n"] = "3.889", - }, + -- Mac Screen Sharing, could probably be used to fingerprint OS + ["RFB 003.889\n"] = "3.889", + }, - sectypes = { - INVALID = 0, - NONE = 1, - VNCAUTH = 2, - RA2 = 5, - RA2NE = 6, - TIGHT = 16, - ULTRA = 17, - TNS = 18, - VENCRYPT = 19, - GTK_VNC_SASL = 20, - MD5 = 21, - COLIN_DEAN_XVP = 22, - MAC_OSX_SECTYPE_30 = 30, - MAC_OSX_SECTYPE_35 = 35, - }, + sectypes = { + INVALID = 0, + NONE = 1, + VNCAUTH = 2, + RA2 = 5, + RA2NE = 6, + TIGHT = 16, + ULTRA = 17, + TNS = 18, + VENCRYPT = 19, + GTK_VNC_SASL = 20, + MD5 = 21, + COLIN_DEAN_XVP = 22, + MAC_OSX_SECTYPE_30 = 30, + MAC_OSX_SECTYPE_35 = 35, + }, - -- Security types are fetched from the rfbproto.pdf - sectypes_str = { - [0] = "Invalid security type", - [1] = "None", - [2] = "VNC Authentication", - [5] = "RA2", - [6] = "RA2ne", - [16]= "Tight", - [17]= "Ultra", - [18]= "TLS", - [19]= "VeNCrypt", - [20]= "GTK-VNC SASL", - [21]= "MD5 hash authentication", - [22]= "Colin Dean xvp", + -- Security types are fetched from the rfbproto.pdf + sectypes_str = { + [0] = "Invalid security type", + [1] = "None", + [2] = "VNC Authentication", + [5] = "RA2", + [6] = "RA2ne", + [16]= "Tight", + [17]= "Ultra", + [18]= "TLS", + [19]= "VeNCrypt", + [20]= "GTK-VNC SASL", + [21]= "MD5 hash authentication", + [22]= "Colin Dean xvp", - -- Mac OS X screen sharing uses 30 and 35 - [30]= "Mac OS X security type", - [35]= "Mac OS X security type", - }, + -- Mac OS X screen sharing uses 30 and 35 + [30]= "Mac OS X security type", + [35]= "Mac OS X security type", + }, - new = function(self, host, port) - local o = { - host = host, - port = port, - socket = nmap.new_socket(), - cli_version = nmap.registry.args['vnc-brute.version'] or "RFB 003.889\n" - } - setmetatable(o, self) - self.__index = self - return o - end, + new = function(self, host, port) + local o = { + host = host, + port = port, + socket = nmap.new_socket(), + cli_version = nmap.registry.args['vnc-brute.version'] or "RFB 003.889\n" + } + setmetatable(o, self) + self.__index = self + return o + end, - --- Connects the VNC socket - connect = function(self) - if ( not(HAVE_SSL) ) then - return false, "The VNC module requires OpenSSL support" - end - return self.socket:connect(self.host, self.port, "tcp") - end, + --- Connects the VNC socket + connect = function(self) + if ( not(HAVE_SSL) ) then + return false, "The VNC module requires OpenSSL support" + end + return self.socket:connect(self.host, self.port, "tcp") + end, - --- Disconnects the VNC socket - disconnect = function(self) - return self.socket:close() - end, + --- Disconnects the VNC socket + disconnect = function(self) + return self.socket:close() + end, - --- Performs the VNC handshake and determines - -- o The RFB Protocol to use - -- o The supported authentication security types - -- - -- @return status, true on success, false on failure - -- @return error string containing error message if status is false - handshake = function(self) - local status, data = self.socket:receive_buf(match.numbytes(12), true) - local vncsec = { - count = 1, - types = {} - } + --- Performs the VNC handshake and determines + -- o The RFB Protocol to use + -- o The supported authentication security types + -- + -- @return status, true on success, false on failure + -- @return error string containing error message if status is false + handshake = function(self) + local status, data = self.socket:receive_buf(match.numbytes(12), true) + local vncsec = { + count = 1, + types = {} + } - if ( not(status) ) then - return status, "ERROR: VNC:handshake failed to receive protocol version" - end + if ( not(status) ) then + return status, "ERROR: VNC:handshake failed to receive protocol version" + end - self.protover = VNC.versions[data] - if ( not(self.protover) ) then - stdnse.print_debug("ERROR: VNC:handshake unsupported version (%s)", data:sub(1,11)) - return false, ("Unsupported version (%s)"):format(data:sub(1,11)) - end + self.protover = VNC.versions[data] + if ( not(self.protover) ) then + stdnse.print_debug("ERROR: VNC:handshake unsupported version (%s)", data:sub(1,11)) + return false, ("Unsupported version (%s)"):format(data:sub(1,11)) + end - status = self.socket:send( self.cli_version ) - if ( not(status) ) then - stdnse.print_debug("ERROR: VNC:handshake failed to send client version") - return false, "ERROR: VNC:handshake failed" - end + status = self.socket:send( self.cli_version ) + if ( not(status) ) then + stdnse.print_debug("ERROR: VNC:handshake failed to send client version") + return false, "ERROR: VNC:handshake failed" + end - local function process_error() - local status, tmp = self.socket:receive_buf(match.numbytes(4), true) - if( not(status) ) then - return false, "VNC:handshake failed to retrieve error message" - end - local len = select(2, bin.unpack(">I", tmp)) - local status, err = self.socket:receive_buf(match.numbytes(len), true) - if( not(status) ) then - return false, "VNC:handshake failed to retrieve error message" - end - return false, err - end + local function process_error() + local status, tmp = self.socket:receive_buf(match.numbytes(4), true) + if( not(status) ) then + return false, "VNC:handshake failed to retrieve error message" + end + local len = select(2, bin.unpack(">I", tmp)) + local status, err = self.socket:receive_buf(match.numbytes(len), true) + if( not(status) ) then + return false, "VNC:handshake failed to retrieve error message" + end + return false, err + end - if ( self.protover == "3.3" ) then - local status, tmp = self.socket:receive_buf(match.numbytes(4), true) - if( not(status) ) then - return false, "VNC:handshake failed to receive security data" - end + if ( self.protover == "3.3" ) then + local status, tmp = self.socket:receive_buf(match.numbytes(4), true) + if( not(status) ) then + return false, "VNC:handshake failed to receive security data" + end - vncsec.types[1] = select(2, bin.unpack("I", tmp) ) - self.vncsec = vncsec + vncsec.types[1] = select(2, bin.unpack("I", tmp) ) + self.vncsec = vncsec - -- do we have an invalid security type, if so we need to handle an - -- error condition - if ( vncsec.types[1] == 0 ) then - return process_error() - end - else - local status, tmp = self.socket:receive_buf(match.numbytes(1), true) - if ( not(status) ) then - stdnse.print_debug("ERROR: VNC:handshake failed to receive security data") - return false, "ERROR: VNC:handshake failed to receive security data" - end + -- do we have an invalid security type, if so we need to handle an + -- error condition + if ( vncsec.types[1] == 0 ) then + return process_error() + end + else + local status, tmp = self.socket:receive_buf(match.numbytes(1), true) + if ( not(status) ) then + stdnse.print_debug("ERROR: VNC:handshake failed to receive security data") + return false, "ERROR: VNC:handshake failed to receive security data" + end - vncsec.count = select(2, bin.unpack("C", tmp)) - if ( vncsec.count == 0 ) then - return process_error() - end - status, tmp = self.socket:receive_buf(match.numbytes(vncsec.count), true) + vncsec.count = select(2, bin.unpack("C", tmp)) + if ( vncsec.count == 0 ) then + return process_error() + end + status, tmp = self.socket:receive_buf(match.numbytes(vncsec.count), true) - if ( not(status) ) then - stdnse.print_debug("ERROR: VNC:handshake failed to receive security data") - return false, "ERROR: VNC:handshake failed to receive security data" - end + if ( not(status) ) then + stdnse.print_debug("ERROR: VNC:handshake failed to receive security data") + return false, "ERROR: VNC:handshake failed to receive security data" + end - for i=1, vncsec.count do - table.insert( vncsec.types, select(2, bin.unpack("C", tmp, i) ) ) - end - self.vncsec = vncsec - end + for i=1, vncsec.count do + table.insert( vncsec.types, select(2, bin.unpack("C", tmp, i) ) ) + end + self.vncsec = vncsec + end - return true - end, + return true + end, - --- Creates the password bit-flip needed before DES encryption - -- - -- @param password string containing the password to process - -- @return password string containing the processed password - createVNCDESKey = function( self, password ) - if ( #password < 8 ) then - for i=1, (8 - #password) do - password = password .. string.char(0x00) - end - end + --- Creates the password bit-flip needed before DES encryption + -- + -- @param password string containing the password to process + -- @return password string containing the processed password + createVNCDESKey = function( self, password ) + if ( #password < 8 ) then + for i=1, (8 - #password) do + password = password .. string.char(0x00) + end + end - local newpass = "" - for i=1, 8 do - local _, bitstr = bin.unpack("B", password, i) - newpass = newpass .. bin.pack("B", bitstr:reverse()) - end - return newpass - end, + local newpass = "" + for i=1, 8 do + local _, bitstr = bin.unpack("B", password, i) + newpass = newpass .. bin.pack("B", bitstr:reverse()) + end + return newpass + end, - --- Attempts to login to the VNC service - -- Currently the only supported auth sectype is VNC Authentication - -- - -- @param username string, could be anything when VNCAuth is used - -- @param password string containing the password to use for authentication - -- @return status true on success, false on failure - -- @return err string containing error message when status is false - login = function( self, username, password ) - if ( not(password) ) then - return false, "No password was supplied" - end + --- Attempts to login to the VNC service + -- Currently the only supported auth sectype is VNC Authentication + -- + -- @param username string, could be anything when VNCAuth is used + -- @param password string containing the password to use for authentication + -- @return status true on success, false on failure + -- @return err string containing error message when status is false + login = function( self, username, password ) + if ( not(password) ) then + return false, "No password was supplied" + end - if ( not( self:supportsSecType( VNC.sectypes.VNCAUTH ) ) ) then - return false, "The server does not support the \"VNC Authentication\" security type." - end + if ( not( self:supportsSecType( VNC.sectypes.VNCAUTH ) ) ) then + return false, "The server does not support the \"VNC Authentication\" security type." + end - -- Announce that we support VNC Authentication - local status = self.socket:send( bin.pack("C", VNC.sectypes.VNCAUTH) ) - if ( not(status) ) then - return false, "Failed to select authentication type" - end + -- Announce that we support VNC Authentication + local status = self.socket:send( bin.pack("C", VNC.sectypes.VNCAUTH) ) + if ( not(status) ) then + return false, "Failed to select authentication type" + end - local status, chall = self.socket:receive_buf(match.numbytes(16), true) - if ( not(status) ) then - return false, "Failed to receive authentication challenge" - end + local status, chall = self.socket:receive_buf(match.numbytes(16), true) + if ( not(status) ) then + return false, "Failed to receive authentication challenge" + end - local key = self:createVNCDESKey(password) - local resp = openssl.encrypt("des-ecb", key, nil, chall, false ) + local key = self:createVNCDESKey(password) + local resp = openssl.encrypt("des-ecb", key, nil, chall, false ) - status = self.socket:send( resp ) - if ( not(status) ) then - return false, "Failed to send authentication response to server" - end + status = self.socket:send( resp ) + if ( not(status) ) then + return false, "Failed to send authentication response to server" + end - local status, result = self.socket:receive_buf(match.numbytes(4), true) - if ( not(status) ) then - return false, "Failed to retrieve authentication status from server" - end + local status, result = self.socket:receive_buf(match.numbytes(4), true) + if ( not(status) ) then + return false, "Failed to retrieve authentication status from server" + end - if ( select(2, bin.unpack("I", result) ) ~= 0 ) then - return false, ("Authentication failed with password %s"):format(password) - end - return true, "" - end, + if ( select(2, bin.unpack("I", result) ) ~= 0 ) then + return false, ("Authentication failed with password %s"):format(password) + end + return true, "" + end, - --- Returns all supported security types as a table - -- - -- @return table containing a entry for each security type - getSecTypesAsTable = function( self ) - local tmp = {} - local typemt = { - __tostring = function(me) - return ("%s (%s)"):format(me.name, me.type) - end - } - for i=1, self.vncsec.count do - local t = {name=VNC.sectypes_str[self.vncsec.types[i]] or "Unknown security type", type=self.vncsec.types[i]} - setmetatable(t, typemt) - table.insert( tmp, t ) - end - return true, tmp - end, + --- Returns all supported security types as a table + -- + -- @return table containing a entry for each security type + getSecTypesAsTable = function( self ) + local tmp = {} + local typemt = { + __tostring = function(me) + return ("%s (%s)"):format(me.name, me.type) + end + } + for i=1, self.vncsec.count do + local t = {name=VNC.sectypes_str[self.vncsec.types[i]] or "Unknown security type", type=self.vncsec.types[i]} + setmetatable(t, typemt) + table.insert( tmp, t ) + end + return true, tmp + end, - --- Checks if the supplied security type is supported or not - -- - -- @param sectype number containing the security type to check for - -- @return status true if supported, false if not supported - supportsSecType = function( self, sectype ) - for i=1, self.vncsec.count do - if ( self.vncsec.types[i] == sectype ) then - return true - end - end - return false - end, + --- Checks if the supplied security type is supported or not + -- + -- @param sectype number containing the security type to check for + -- @return status true if supported, false if not supported + supportsSecType = function( self, sectype ) + for i=1, self.vncsec.count do + if ( self.vncsec.types[i] == sectype ) then + return true + end + end + return false + end, - --- Returns the protocol version reported by the server - -- - -- @param version string containing the version number - getProtocolVersion = function( self ) - return self.protover - end, + --- Returns the protocol version reported by the server + -- + -- @param version string containing the version number + getProtocolVersion = function( self ) + return self.protover + end, } diff --git a/nselib/vuzedht.lua b/nselib/vuzedht.lua index ddc819e54..1e738ae98 100644 --- a/nselib/vuzedht.lua +++ b/nselib/vuzedht.lua @@ -35,364 +35,364 @@ _ENV = stdnse.module("vuzedht", stdnse.seeall) Request = { - Actions = { - ACTION_PING = 1024, - FIND_NODE = 1028, - }, + Actions = { + ACTION_PING = 1024, + FIND_NODE = 1028, + }, - -- The request Header class shared by all Requests classes - Header = { + -- The request Header class shared by all Requests classes + Header = { - -- Creates a new Header instance - -- @param action number containing the request action - -- @param session instance of Session - -- @return o new instance of Header - new = function(self, action, session) - local o = { - conn_id = string.char(255) .. openssl.rand_pseudo_bytes(7), - -- we need to handle this one like this, due to a bug in nsedoc - -- it used to be action = action, but that breaks parsing - ["action"] = action, - trans_id = session:getTransactionId(), - proto_version = 0x32, - vendor_id = 0, - network_id = 0, - local_proto_version = 0x32, - address = session:getAddress(), - port = session:getPort(), - instance_id = session:getInstanceId(), - time = os.time(), - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Header instance + -- @param action number containing the request action + -- @param session instance of Session + -- @return o new instance of Header + new = function(self, action, session) + local o = { + conn_id = string.char(255) .. openssl.rand_pseudo_bytes(7), + -- we need to handle this one like this, due to a bug in nsedoc + -- it used to be action = action, but that breaks parsing + ["action"] = action, + trans_id = session:getTransactionId(), + proto_version = 0x32, + vendor_id = 0, + network_id = 0, + local_proto_version = 0x32, + address = session:getAddress(), + port = session:getPort(), + instance_id = session:getInstanceId(), + time = os.time(), + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the header to a string - __tostring = function(self) - local lhost = ipOps.todword(self.address) - return bin.pack( ">AIICCICCISIL", self.conn_id, self.action, self.trans_id, - self.proto_version, self.vendor_id, self.network_id, self.local_proto_version, - 4, lhost, self.port, self.instance_id, self.time ) - end, + -- Converts the header to a string + __tostring = function(self) + local lhost = ipOps.todword(self.address) + return bin.pack( ">AIICCICCISIL", self.conn_id, self.action, self.trans_id, + self.proto_version, self.vendor_id, self.network_id, self.local_proto_version, + 4, lhost, self.port, self.instance_id, self.time ) + end, - }, + }, - -- The PING Request class - Ping = { + -- The PING Request class + Ping = { - -- Creates a new Ping instance - -- @param session instance of Session - -- @return o new instance of Ping - new = function(self, session) - local o = { - header = Request.Header:new(Request.Actions.ACTION_PING, session) - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Ping instance + -- @param session instance of Session + -- @return o new instance of Ping + new = function(self, session) + local o = { + header = Request.Header:new(Request.Actions.ACTION_PING, session) + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts a Ping Request to a string - __tostring = function(self) - return tostring(self.header) - end, + -- Converts a Ping Request to a string + __tostring = function(self) + return tostring(self.header) + end, - }, + }, - -- The FIND_NODES Request class - FindNode = { + -- The FIND_NODES Request class + FindNode = { - -- Creates a new FindNode instance - -- @param session instance of Session - -- @return o new instance of FindNode - new = function(self, session) - local o = { - header = Request.Header:new(Request.Actions.FIND_NODE, session), - id_length = 20, - node_id = '\xA7' .. openssl.rand_pseudo_bytes(19), - status = 0xFFFFFFFF, - dht_size = 0, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new FindNode instance + -- @param session instance of Session + -- @return o new instance of FindNode + new = function(self, session) + local o = { + header = Request.Header:new(Request.Actions.FIND_NODE, session), + id_length = 20, + node_id = '\xA7' .. openssl.rand_pseudo_bytes(19), + status = 0xFFFFFFFF, + dht_size = 0, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts a FindNode Request to a string - __tostring = function(self) - local data = tostring(self.header) - data = data .. bin.pack(">CAII", self.id_length, self.node_id, self.status, self.dht_size) - return data - end, - } + -- Converts a FindNode Request to a string + __tostring = function(self) + local data = tostring(self.header) + data = data .. bin.pack(">CAII", self.id_length, self.node_id, self.status, self.dht_size) + return data + end, + } } Response = { - -- A table of currently supported Actions (Responses) - -- It's used in the fromString method to determine which class to create. - Actions = { - ACTION_PING = 1025, - FIND_NODE = 1029, - ERROR = 1032, - }, + -- A table of currently supported Actions (Responses) + -- It's used in the fromString method to determine which class to create. + Actions = { + ACTION_PING = 1025, + FIND_NODE = 1029, + ERROR = 1032, + }, - -- Creates an address record based on received data - -- @param data containing an address record [C][I|H][S] where - -- [C] is the length of the address (4 or 16) - -- [I|H] is the address as a dword or hex string - -- [S] is the port number as a short - -- @return o Address instance on success, nil on failure - Address = { - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- Creates an address record based on received data + -- @param data containing an address record [C][I|H][S] where + -- [C] is the length of the address (4 or 16) + -- [I|H] is the address as a dword or hex string + -- [S] is the port number as a short + -- @return o Address instance on success, nil on failure + Address = { + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- Parses the received data - -- @return true on success, false on failure - parse = function(self) - local pos, addr_len = bin.unpack("C", self.data) - if ( addr_len == 4 ) then - self.length = 4 + 2 + 1 - pos, self.ip = bin.unpack("S", self.data, pos) - return true - end - }, + -- Parses the received data + -- @return true on success, false on failure + parse = function(self) + local pos, addr_len = bin.unpack("C", self.data) + if ( addr_len == 4 ) then + self.length = 4 + 2 + 1 + pos, self.ip = bin.unpack("S", self.data, pos) + return true + end + }, - -- The reponse header, present in all packets - Header = { + -- The reponse header, present in all packets + Header = { - Vendors = { - [0] = "Azureus", - [1] = "ShareNet", - [255] = "Unknown", -- to be honest, we report all except 0 and 1 as unknown - }, + Vendors = { + [0] = "Azureus", + [1] = "ShareNet", + [255] = "Unknown", -- to be honest, we report all except 0 and 1 as unknown + }, - Networks = { - [0] = "Stable", - [1] = "CVS" - }, + Networks = { + [0] = "Stable", + [1] = "CVS" + }, - -- Creates a new Header instance - -- @param data string containing the received data - -- @return o instance of Header - new = function(self, data) - local o = { data = data } - setmetatable(o, self) - self.__index = self - o:parse() - return o - end, + -- Creates a new Header instance + -- @param data string containing the received data + -- @return o instance of Header + new = function(self, data) + local o = { data = data } + setmetatable(o, self) + self.__index = self + o:parse() + return o + end, - -- parses the header - parse = function(self) - local pos - pos, self.action, self.trans_id, self.conn_id, - self.proto_version, self.vendor_id, self.network_id, - self.instance_id = bin.unpack(">IIH8CCII", self.data) - end, + -- parses the header + parse = function(self) + local pos + pos, self.action, self.trans_id, self.conn_id, + self.proto_version, self.vendor_id, self.network_id, + self.instance_id = bin.unpack(">IIH8CCII", self.data) + end, - -- Converts the header to a suitable string representation - __tostring = function(self) - local result = {} - table.insert(result, ("Transaction id: %d"):format(self.trans_id)) - table.insert(result, ("Connection id: 0x%s"):format(self.conn_id)) - table.insert(result, ("Protocol version: %d"):format(self.proto_version)) - table.insert(result, ("Vendor id: %s (%d)"):format( - Response.Header.Vendors[self.vendor_id] or "Unknown", self.vendor_id)) - table.insert(result, ("Network id: %s (%d)"):format( - Response.Header.Networks[self.network_id] or "Unknown", self.network_id)) - table.insert(result, ("Instance id: %d"):format(self.instance_id)) - return stdnse.format_output(true, result) - end, + -- Converts the header to a suitable string representation + __tostring = function(self) + local result = {} + table.insert(result, ("Transaction id: %d"):format(self.trans_id)) + table.insert(result, ("Connection id: 0x%s"):format(self.conn_id)) + table.insert(result, ("Protocol version: %d"):format(self.proto_version)) + table.insert(result, ("Vendor id: %s (%d)"):format( + Response.Header.Vendors[self.vendor_id] or "Unknown", self.vendor_id)) + table.insert(result, ("Network id: %s (%d)"):format( + Response.Header.Networks[self.network_id] or "Unknown", self.network_id)) + table.insert(result, ("Instance id: %d"):format(self.instance_id)) + return stdnse.format_output(true, result) + end, - }, + }, - -- The PING response - PING = { + -- The PING response + PING = { - -- Creates a new instance of PING - -- @param data string containing the received data - -- @return o new PING instance - new = function(self, data) - local o = { - header = Response.Header:new(data) - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of PING + -- @param data string containing the received data + -- @return o new PING instance + new = function(self, data) + local o = { + header = Response.Header:new(data) + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Creates a new PING instance based on received data - -- @param data string containing received data - -- @return status true on success, false on failure - -- @return new instance of PING on success, error message on failure - fromString = function(data) - local ping = Response.PING:new(data) - if ( ping ) then - return true, ping - end - return false, "Failed to parse PING response" - end, + -- Creates a new PING instance based on received data + -- @param data string containing received data + -- @return status true on success, false on failure + -- @return new instance of PING on success, error message on failure + fromString = function(data) + local ping = Response.PING:new(data) + if ( ping ) then + return true, ping + end + return false, "Failed to parse PING response" + end, - -- Converts the PING response to a response suitable for script output - -- @return result formatted script output - __tostring = function(self) - return tostring(self.header) - end, - }, + -- Converts the PING response to a response suitable for script output + -- @return result formatted script output + __tostring = function(self) + return tostring(self.header) + end, + }, - -- A class to process the response from a FIND_NODE query - FIND_NODE = { + -- A class to process the response from a FIND_NODE query + FIND_NODE = { - -- Creates a new FIND_NODE instance - -- @param data string containing the received data - -- @return o new instance of FIND_NODE - new = function(self, data) - local o = { - header = Response.Header:new(data), - data = data:sub(27) - } - setmetatable(o, self) - self.__index = self - o:parse() - return o - end, + -- Creates a new FIND_NODE instance + -- @param data string containing the received data + -- @return o new instance of FIND_NODE + new = function(self, data) + local o = { + header = Response.Header:new(data), + data = data:sub(27) + } + setmetatable(o, self) + self.__index = self + o:parse() + return o + end, - -- Parses the FIND_NODE response - parse = function(self) - local pos - pos, self.spoof_id, self.node_type, self.dht_size, - self.network_coords = bin.unpack(">IIIH20", self.data) + -- Parses the FIND_NODE response + parse = function(self) + local pos + pos, self.spoof_id, self.node_type, self.dht_size, + self.network_coords = bin.unpack(">IIIH20", self.data) - local contact_count - pos, contact_count = bin.unpack("C", self.data, pos) - self.contacts = {} - for i=1, contact_count do - local contact, addr_len, address = {} - pos, contact.type, contact.proto_version, addr_len = bin.unpack("CCC", self.data, pos) + local contact_count + pos, contact_count = bin.unpack("C", self.data, pos) + self.contacts = {} + for i=1, contact_count do + local contact, addr_len, address = {} + pos, contact.type, contact.proto_version, addr_len = bin.unpack("CCC", self.data, pos) - if ( addr_len == 4 ) then - pos, address = bin.unpack("S", self.data, pos) - table.insert(self.contacts, contact) - end - end, + if ( addr_len == 4 ) then + pos, address = bin.unpack("S", self.data, pos) + table.insert(self.contacts, contact) + end + end, - -- Creates a new instance of FIND_NODE based on received data - -- @param data string containing received data - -- @return status true on success, false on failure - -- @return new instance of FIND_NODE on success, error message on failure - fromString = function(data) - local find = Response.FIND_NODE:new(data) - if ( find.header.proto_version < 13 ) then - stdnse.print_debug("ERROR: Unsupported version %d", find.header.proto_version) - return false - end + -- Creates a new instance of FIND_NODE based on received data + -- @param data string containing received data + -- @return status true on success, false on failure + -- @return new instance of FIND_NODE on success, error message on failure + fromString = function(data) + local find = Response.FIND_NODE:new(data) + if ( find.header.proto_version < 13 ) then + stdnse.print_debug("ERROR: Unsupported version %d", find.header.proto_version) + return false + end - return true, find - end, + return true, find + end, - -- Convert the FIND_NODE response to formatted string data, suitable - -- for script output. - -- @return string with formatted FIND_NODE data - __tostring = function(self) - if ( not(self.contacts) ) then - return "" - end + -- Convert the FIND_NODE response to formatted string data, suitable + -- for script output. + -- @return string with formatted FIND_NODE data + __tostring = function(self) + if ( not(self.contacts) ) then + return "" + end - local result = {} - for _, contact in ipairs(self.contacts) do - table.insert(result, ("%s:%d"):format(contact.address, contact.port)) - end - return stdnse.format_output(true, result) - end - }, + local result = {} + for _, contact in ipairs(self.contacts) do + table.insert(result, ("%s:%d"):format(contact.address, contact.port)) + end + return stdnse.format_output(true, result) + end + }, - -- The ERROR action - ERROR = { + -- The ERROR action + ERROR = { - -- Creates a new ERROR instance based on received socket data - -- @return o new ERROR instance on success, nil on failure - new = function(self, data) - local o = { - header = Response.Header:new(data), - data = data:sub(27) - } - setmetatable(o, self) - self.__index = self - if ( o:parse() ) then - return o - end - end, + -- Creates a new ERROR instance based on received socket data + -- @return o new ERROR instance on success, nil on failure + new = function(self, data) + local o = { + header = Response.Header:new(data), + data = data:sub(27) + } + setmetatable(o, self) + self.__index = self + if ( o:parse() ) then + return o + end + end, - -- parses the received data and attempts to create an ERROR response - -- @return true on success, false on failure - parse = function(self) - local pos, err_type = bin.unpack(">I", self.data) - if ( 1 == err_type ) then - self.addr = Response.Address:new(self.data:sub(5)) - return true - end - return false - end, + -- parses the received data and attempts to create an ERROR response + -- @return true on success, false on failure + parse = function(self) + local pos, err_type = bin.unpack(">I", self.data) + if ( 1 == err_type ) then + self.addr = Response.Address:new(self.data:sub(5)) + return true + end + return false + end, - -- creates a new ERROR instance based on the received data - -- @return true on success, false on failure - fromString = function(data) - local err = Response.ERROR:new(data) - if ( err ) then - return true, err - end - return false - end, + -- creates a new ERROR instance based on the received data + -- @return true on success, false on failure + fromString = function(data) + local err = Response.ERROR:new(data) + if ( err ) then + return true, err + end + return false + end, - -- Converts the ERROR action to a formatted response - -- @return string containing the formatted response - __tostring = function(self) - return ("Wrong address, expected: %s"):format(self.addr.ip) - end, + -- Converts the ERROR action to a formatted response + -- @return string containing the formatted response + __tostring = function(self) + return ("Wrong address, expected: %s"):format(self.addr.ip) + end, - }, + }, - -- creates a suitable Response class based on the Action received - -- @return true on success, false on failure - -- @return response instance of suitable Response class on success, - -- err string error message if status is false - fromString = function(data) - local pos, action = bin.unpack(">I", data) + -- creates a suitable Response class based on the Action received + -- @return true on success, false on failure + -- @return response instance of suitable Response class on success, + -- err string error message if status is false + fromString = function(data) + local pos, action = bin.unpack(">I", data) - if ( action == Response.Actions.ACTION_PING ) then - return Response.PING.fromString(data) - elseif ( action == Response.Actions.FIND_NODE ) then - return Response.FIND_NODE.fromString(data) - elseif ( action == Response.Actions.ERROR ) then - return Response.ERROR.fromString(data) - end + if ( action == Response.Actions.ACTION_PING ) then + return Response.PING.fromString(data) + elseif ( action == Response.Actions.FIND_NODE ) then + return Response.FIND_NODE.fromString(data) + elseif ( action == Response.Actions.ERROR ) then + return Response.ERROR.fromString(data) + end - stdnse.print_debug("ERROR: Unknown response received from server") - return false, "Failed to parse response" - end, + stdnse.print_debug("ERROR: Unknown response received from server") + return false, "Failed to parse response" + end, @@ -401,157 +401,157 @@ Response = { -- The Session Session = { - -- Creates a new Session instance to keep track on some of the protocol - -- stuff, such as transaction- and instance- identities. - -- @param address the local address to pass in the requests to the server - -- this could be either the local address or the IP of the router - -- depending on if NAT is used or not. - -- @param port the local port to pass in the requests to the server - -- @return o new instance of Session - new = function(self, address, port) - local o = { - trans_id = math.random(12345678), - instance_id = math.random(12345678), - address = address, - port = port, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new Session instance to keep track on some of the protocol + -- stuff, such as transaction- and instance- identities. + -- @param address the local address to pass in the requests to the server + -- this could be either the local address or the IP of the router + -- depending on if NAT is used or not. + -- @param port the local port to pass in the requests to the server + -- @return o new instance of Session + new = function(self, address, port) + local o = { + trans_id = math.random(12345678), + instance_id = math.random(12345678), + address = address, + port = port, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Gets the next transaction ID - -- @return trans_id number - getTransactionId = function(self) - self.trans_id = self.trans_id + 1 - return self.trans_id - end, + -- Gets the next transaction ID + -- @return trans_id number + getTransactionId = function(self) + self.trans_id = self.trans_id + 1 + return self.trans_id + end, - -- Gets the next instance ID - -- @return instance_id number - getInstanceId = function(self) - self.instance_id = self.instance_id + 1 - return self.instance_id - end, + -- Gets the next instance ID + -- @return instance_id number + getInstanceId = function(self) + self.instance_id = self.instance_id + 1 + return self.instance_id + end, - -- Gets the stored local address used to create the session - -- @return string containing the IP passed to the session - getAddress = function(self) - return self.address - end, + -- Gets the stored local address used to create the session + -- @return string containing the IP passed to the session + getAddress = function(self) + return self.address + end, - -- Get the stored local port used to create the session - -- @return number containing the local port - getPort = function(self) - return self.port - end + -- Get the stored local port used to create the session + -- @return number containing the local port + getPort = function(self) + return self.port + end } -- The Helper class, used as main interface between the scripts and the library Helper = { - -- Creates a new instance of the Helper class - -- @param host table as passed to the action method - -- @param port table as passed to the action method - -- @param lhost [optional] used if an alternate local address is to be - -- passed in the requests to the remote node (ie. NAT is in play). - -- @param lport [optional] used if an alternate port is to be passed in - -- the requests to the remote node. - -- @return o new instance of Helper - new = function(self, host, port, lhost, lport) - local o = { - host = host, - port = port, - lhost = lhost, - lport = lport - } - setmetatable(o, self) - self.__index = self - math.randomseed(os.time()) - return o - end, + -- Creates a new instance of the Helper class + -- @param host table as passed to the action method + -- @param port table as passed to the action method + -- @param lhost [optional] used if an alternate local address is to be + -- passed in the requests to the remote node (ie. NAT is in play). + -- @param lport [optional] used if an alternate port is to be passed in + -- the requests to the remote node. + -- @return o new instance of Helper + new = function(self, host, port, lhost, lport) + local o = { + host = host, + port = port, + lhost = lhost, + lport = lport + } + setmetatable(o, self) + self.__index = self + math.randomseed(os.time()) + return o + end, - -- Connects to the remote Vuze Node - -- @return true on success, false on failure - -- @return err string error message if status is false - connect = function(self) - local lhost = self.lhost or stdnse.get_script_args('vuzedht.lhost') - local lport = self.lport or stdnse.get_script_args('vuzedht.lport') + -- Connects to the remote Vuze Node + -- @return true on success, false on failure + -- @return err string error message if status is false + connect = function(self) + local lhost = self.lhost or stdnse.get_script_args('vuzedht.lhost') + local lport = self.lport or stdnse.get_script_args('vuzedht.lport') - self.socket = nmap.new_socket() + self.socket = nmap.new_socket() - if ( lport ) then - self.socket:bind(nil, lport) - end - local status, err = self.socket:connect(self.host, self.port) - if ( not(status) ) then - return false, "Failed to connect to server" - end + if ( lport ) then + self.socket:bind(nil, lport) + end + local status, err = self.socket:connect(self.host, self.port) + if ( not(status) ) then + return false, "Failed to connect to server" + end - if ( not(lhost) or not(lport) ) then - local status, lh, lp, _, _ = self.socket:get_info() - if ( not(status) ) then - return false, "Failed to get socket information" - end - lhost = lhost or lh - lport = lport or lp - end + if ( not(lhost) or not(lport) ) then + local status, lh, lp, _, _ = self.socket:get_info() + if ( not(status) ) then + return false, "Failed to get socket information" + end + lhost = lhost or lh + lport = lport or lp + end - self.session = Session:new(lhost, lport) - return true - end, + self.session = Session:new(lhost, lport) + return true + end, - -- Sends a Vuze PING request to the server and parses the response - -- @return status true on succes, false on failure - -- @return response PING response instance on success, - -- err string containing the error message on failure - ping = function(self) - local ping = Request.Ping:new(self.session) - local status, err = self.socket:send(tostring(ping)) - if ( not(status) ) then - return false, "Failed to send PING request to server" - end + -- Sends a Vuze PING request to the server and parses the response + -- @return status true on succes, false on failure + -- @return response PING response instance on success, + -- err string containing the error message on failure + ping = function(self) + local ping = Request.Ping:new(self.session) + local status, err = self.socket:send(tostring(ping)) + if ( not(status) ) then + return false, "Failed to send PING request to server" + end - local data - status, data = self.socket:receive() - if ( not(status) ) then - return false, "Failed to receive PING response from server" - end - local response - status, response = Response.fromString(data) - if ( not(status) ) then - return false, "Failed to parse PING response from server" - end - return true, response - end, + local data + status, data = self.socket:receive() + if ( not(status) ) then + return false, "Failed to receive PING response from server" + end + local response + status, response = Response.fromString(data) + if ( not(status) ) then + return false, "Failed to parse PING response from server" + end + return true, response + end, - -- Requests a list of known nodes by sending the FIND_NODES request - -- to the remote node and parses the response. - -- @return status true on success, false on failure - -- @return response FIND_NODE response instance on success - -- err string containing the error message on failure - findNodes = function(self) - local find = Request.FindNode:new(self.session) - local status, err = self.socket:send(tostring(find)) - if ( not(status) ) then - return false, "Failed to send FIND_NODE request to server" - end + -- Requests a list of known nodes by sending the FIND_NODES request + -- to the remote node and parses the response. + -- @return status true on success, false on failure + -- @return response FIND_NODE response instance on success + -- err string containing the error message on failure + findNodes = function(self) + local find = Request.FindNode:new(self.session) + local status, err = self.socket:send(tostring(find)) + if ( not(status) ) then + return false, "Failed to send FIND_NODE request to server" + end - local data - status, data = self.socket:receive() - local response - status, response = Response.fromString(data) - if ( not(status) ) then - return false, "Failed to parse FIND_NODE response from server" - end - return true, response - end, + local data + status, data = self.socket:receive() + local response + status, response = Response.fromString(data) + if ( not(status) ) then + return false, "Failed to parse FIND_NODE response from server" + end + return true, response + end, - -- Closes the socket connect to the remote node - close = function(self) - self.socket:close() - end, + -- Closes the socket connect to the remote node + close = function(self) + self.socket:close() + end, } return _ENV; diff --git a/nselib/wsdd.lua b/nselib/wsdd.lua index d0fd63cb7..fdbf15973 100644 --- a/nselib/wsdd.lua +++ b/nselib/wsdd.lua @@ -23,9 +23,9 @@ -- -- The following code snipplet shows how the library can be used: -- --- local helper = wsdd.Helper:new() --- helper:setMulticast(true) --- return stdnse.format_output( helper:discoverDevices() ) +-- local helper = wsdd.Helper:new() +-- helper:setMulticast(true) +-- return stdnse.format_output( helper:discoverDevices() ) -- -- -- @author "Patrik Karlsson " @@ -44,47 +44,47 @@ local HAVE_SSL, openssl = pcall(require,'openssl') -- The different probes local probes = { - -- Detects devices supporting the WSDD protocol - { - name = 'general', - desc = 'Devices', - data = '' .. - '' .. - '' .. - 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' .. - '' .. - 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' .. - 'urn:uuid:#uuid#' .. - '' - }, + -- Detects devices supporting the WSDD protocol + { + name = 'general', + desc = 'Devices', + data = '' .. + '' .. + '' .. + 'urn:schemas-xmlsoap-org:ws:2005:04:discovery' .. + '' .. + 'http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe' .. + 'urn:uuid:#uuid#' .. + '' + }, - -- Detects Windows Communication Framework (WCF) web services - { - name = 'wcf', - desc = 'WCF Services', - data = '' .. - '' .. - '' .. - 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe' .. - '' .. - 'urn:uuid:#uuid#' .. - '' .. - 'urn:docs-oasis-open-org:ws-dd:ns:discovery:2009:01' .. - '' .. - '' .. - '' .. - '' .. - '' .. - 'PT20S' .. - '' .. - '' .. - '' .. - '', - } + -- Detects Windows Communication Framework (WCF) web services + { + name = 'wcf', + desc = 'WCF Services', + data = '' .. + '' .. + '' .. + 'http://docs.oasis-open.org/ws-dd/ns/discovery/2009/01/Probe' .. + '' .. + 'urn:uuid:#uuid#' .. + '' .. + 'urn:docs-oasis-open-org:ws-dd:ns:discovery:2009:01' .. + '' .. + '' .. + '' .. + '' .. + '' .. + 'PT20S' .. + '' .. + '' .. + '' .. + '', + } } -- A table that keeps track of received probe matches @@ -92,291 +92,291 @@ local probe_matches = {} Util = { - --- Creates a UUID - -- - -- @return uuid string containing a uuid - generateUUID = function() - local rnd_bytes = select(2, bin.unpack( "H16", openssl.rand_bytes( 16 ) ) ):lower() + --- Creates a UUID + -- + -- @return uuid string containing a uuid + generateUUID = function() + local rnd_bytes = select(2, bin.unpack( "H16", openssl.rand_bytes( 16 ) ) ):lower() - return ("%s-%s-%s-%s-%s"):format( rnd_bytes:sub(1, 8), - rnd_bytes:sub(9, 12), rnd_bytes:sub( 13, 16 ), rnd_bytes:sub( 17, 20 ), - rnd_bytes:sub(21, 32) ) - end, + return ("%s-%s-%s-%s-%s"):format( rnd_bytes:sub(1, 8), + rnd_bytes:sub(9, 12), rnd_bytes:sub( 13, 16 ), rnd_bytes:sub( 17, 20 ), + rnd_bytes:sub(21, 32) ) + end, - --- Retrieves a probe from the probes table by name - -- - -- @param name string containing the name of the probe to retrieve - -- @return probe table containing the probe or nil if not found - getProbeByName = function( name ) - for _, probe in ipairs(probes) do - if ( probe.name == name ) then - return probe - end - end - return - end, + --- Retrieves a probe from the probes table by name + -- + -- @param name string containing the name of the probe to retrieve + -- @return probe table containing the probe or nil if not found + getProbeByName = function( name ) + for _, probe in ipairs(probes) do + if ( probe.name == name ) then + return probe + end + end + return + end, - getProbes = function() return probes end, + getProbes = function() return probes end, - sha1sum = function(data) return openssl.sha1(data) end + sha1sum = function(data) return openssl.sha1(data) end } Decoders = { - --- Decodes a wcf probe response - -- - -- @param data string containing the response as received over the wire - -- @return status true on success, false on failure - -- @return response table containing the following fields - -- msgid, xaddrs, types - -- err string containing the error message - ['wcf'] = function( data ) - local response = {} + --- Decodes a wcf probe response + -- + -- @param data string containing the response as received over the wire + -- @return status true on success, false on failure + -- @return response table containing the following fields + -- msgid, xaddrs, types + -- err string containing the error message + ['wcf'] = function( data ) + local response = {} - -- extracts the messagid, so we can check if we already got a response - response.msgid = data:match("<.*:MessageID>urn:uuid:(.*)") + -- extracts the messagid, so we can check if we already got a response + response.msgid = data:match("<.*:MessageID>urn:uuid:(.*)") - -- if unable to parse msgid return nil - if ( not(response.msgid) ) then - return false, "No message id was found" - end + -- if unable to parse msgid return nil + if ( not(response.msgid) ) then + return false, "No message id was found" + end - response.xaddrs = data:match("<.*:*XAddrs>(.*)") - response.types = data:match("<.*:Types>[wsdp:]*(.*)") + response.xaddrs = data:match("<.*:*XAddrs>(.*)") + response.types = data:match("<.*:Types>[wsdp:]*(.*)") - return true, response - end, + return true, response + end, - --- Decodes a general probe response - -- - -- @param data string containing the response as received over the wire - -- @return status true on success, false on failure - -- @return response table containing the following fields - -- msgid, xaddrs, types - -- err string containing the error message - ['general'] = function( data ) - return Decoders['wcf'](data) - end, + --- Decodes a general probe response + -- + -- @param data string containing the response as received over the wire + -- @return status true on success, false on failure + -- @return response table containing the following fields + -- msgid, xaddrs, types + -- err string containing the error message + ['general'] = function( data ) + return Decoders['wcf'](data) + end, - --- Decodes an error message received from the service - -- - -- @param data string containing the response as received over the wire - -- @return status true on success, false on failure - -- @return err string containing the error message - ['error'] = function( data ) - local response = "Failed to decode response from device: " - local err = data:match("(.-)<") - response = response .. (err or "Unknown error") + --- Decodes an error message received from the service + -- + -- @param data string containing the response as received over the wire + -- @return status true on success, false on failure + -- @return err string containing the error message + ['error'] = function( data ) + local response = "Failed to decode response from device: " + local err = data:match("(.-)<") + response = response .. (err or "Unknown error") - return true, response - end, + return true, response + end, } Comm = { - --- Creates a new Comm instance - -- - -- @param host string containing the host name or ip - -- @param port number containing the port to connect to - -- @return o a new instance of Comm - new = function( self, host, port, mcast ) - local o = {} - setmetatable(o, self) - self.__index = self - o.host = host - o.port = port - o.mcast = mcast or false - o.sendcount = 2 - o.timeout = 5000 - return o - end, + --- Creates a new Comm instance + -- + -- @param host string containing the host name or ip + -- @param port number containing the port to connect to + -- @return o a new instance of Comm + new = function( self, host, port, mcast ) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.mcast = mcast or false + o.sendcount = 2 + o.timeout = 5000 + return o + end, - --- Sets the timeout for socket reads - setTimeout = function( self, timeout ) self.timeout = timeout end, + --- Sets the timeout for socket reads + setTimeout = function( self, timeout ) self.timeout = timeout end, - --- Sends a probe over the wire - -- - -- @return status true on success, false on failure - sendProbe = function( self ) - local status, err + --- Sends a probe over the wire + -- + -- @return status true on success, false on failure + sendProbe = function( self ) + local status, err - -- replace all instances of #uuid# in the probe - local probedata = self.probe.data:gsub("#uuid#", Util.generateUUID()) + -- replace all instances of #uuid# in the probe + local probedata = self.probe.data:gsub("#uuid#", Util.generateUUID()) - if ( self.mcast ) then - self.socket = nmap.new_socket("udp") - self.socket:set_timeout(self.timeout) - else - self.socket = nmap.new_socket() - self.socket:set_timeout(self.timeout) - status, err = self.socket:connect( self.host, self.port, "udp" ) - if ( not(status) ) then return err end - end + if ( self.mcast ) then + self.socket = nmap.new_socket("udp") + self.socket:set_timeout(self.timeout) + else + self.socket = nmap.new_socket() + self.socket:set_timeout(self.timeout) + status, err = self.socket:connect( self.host, self.port, "udp" ) + if ( not(status) ) then return err end + end - for i=1, self.sendcount do - if ( self.mcast ) then - status, err = self.socket:sendto( self.host, self.port, probedata ) - else - status, err = self.socket:send( probedata ) - end - if ( not(status) ) then return err end - end - return true - end, + for i=1, self.sendcount do + if ( self.mcast ) then + status, err = self.socket:sendto( self.host, self.port, probedata ) + else + status, err = self.socket:send( probedata ) + end + if ( not(status) ) then return err end + end + return true + end, - --- Sets a probe from the probes table to send - -- - -- @param probe table containing a probe from probes - setProbe = function( self, probe ) - self.probe = probe - end, + --- Sets a probe from the probes table to send + -- + -- @param probe table containing a probe from probes + setProbe = function( self, probe ) + self.probe = probe + end, - --- Receives one or more responses for a Probe - -- - -- @return table containing decoded responses suitable for - -- stdnse.format_output - recvProbeMatches = function( self ) - local responses = {} - repeat - local data + --- Receives one or more responses for a Probe + -- + -- @return table containing decoded responses suitable for + -- stdnse.format_output + recvProbeMatches = function( self ) + local responses = {} + repeat + local data - local status, data = self.socket:receive() - if ( not(status) ) then - if ( data == "TIMEOUT" ) then - break - else - return false, data - end - end + local status, data = self.socket:receive() + if ( not(status) ) then + if ( data == "TIMEOUT" ) then + break + else + return false, data + end + end - local _, ip - status, _, _, ip, _ = self.socket:get_info() - if( not(status) ) then - stdnse.print_debug( 3, "wsdd.recvProbeMatches: ERROR: Failed to get socket info" ) - return false, "ERROR: Failed to get socket info" - end + local _, ip + status, _, _, ip, _ = self.socket:get_info() + if( not(status) ) then + stdnse.print_debug( 3, "wsdd.recvProbeMatches: ERROR: Failed to get socket info" ) + return false, "ERROR: Failed to get socket info" + end - -- push the unparsed response to the response table - local status, response = Decoders[self.probe.name]( data ) - local id, output - -- if we failed to decode the response indicate this - if ( status ) then - output = {} - table.insert(output, "Message id: " .. response.msgid) - if ( response.xaddrs ) then - table.insert(output, "Address: " .. response.xaddrs) - end - if ( response.types ) then - table.insert(output, "Type: " .. response.types) - end - id = response.msgid - else - status, response = Decoders["error"](data) - output = response - id = Util.sha1sum(data) - end + -- push the unparsed response to the response table + local status, response = Decoders[self.probe.name]( data ) + local id, output + -- if we failed to decode the response indicate this + if ( status ) then + output = {} + table.insert(output, "Message id: " .. response.msgid) + if ( response.xaddrs ) then + table.insert(output, "Address: " .. response.xaddrs) + end + if ( response.types ) then + table.insert(output, "Type: " .. response.types) + end + id = response.msgid + else + status, response = Decoders["error"](data) + output = response + id = Util.sha1sum(data) + end - if ( self.mcast and not(probe_matches[id]) ) then - if target.ALLOW_NEW_TARGETS then target.add(ip) end - table.insert( responses, { name=ip, output } ) - elseif ( not(probe_matches[id]) ) then - responses = output - end + if ( self.mcast and not(probe_matches[id]) ) then + if target.ALLOW_NEW_TARGETS then target.add(ip) end + table.insert( responses, { name=ip, output } ) + elseif ( not(probe_matches[id]) ) then + responses = output + end - -- avoid duplicates - probe_matches[id] = true - until( not(self.mcast) ) + -- avoid duplicates + probe_matches[id] = true + until( not(self.mcast) ) - -- we're done with the socket - self.socket:close() + -- we're done with the socket + self.socket:close() - return true, responses - end + return true, responses + end } Helper = { - --- Creates a new helper instance - -- - -- @param host string containing the host name or ip - -- @param port number containing the port to connect to - -- @return o a new instance of Helper - new = function( self, host, port ) - local o = {} - setmetatable(o, self) - self.__index = self - o.host = host - o.port = port - o.mcast = false - o.timeout = 5000 - return o - end, + --- Creates a new helper instance + -- + -- @param host string containing the host name or ip + -- @param port number containing the port to connect to + -- @return o a new instance of Helper + new = function( self, host, port ) + local o = {} + setmetatable(o, self) + self.__index = self + o.host = host + o.port = port + o.mcast = false + o.timeout = 5000 + return o + end, - --- Instructs the helper to use unconnected sockets supporting multicast - -- - -- @param mcast boolean true if multicast is to be used, false otherwise - setMulticast = function( self, mcast ) - assert( type(mcast)=="boolean", "mcast has to be either true or false") - local family = nmap.address_family() - self.mcast = mcast - self.host = (family=="inet6" and "FF02::C" or "239.255.255.250") - self.port = 3702 - end, + --- Instructs the helper to use unconnected sockets supporting multicast + -- + -- @param mcast boolean true if multicast is to be used, false otherwise + setMulticast = function( self, mcast ) + assert( type(mcast)=="boolean", "mcast has to be either true or false") + local family = nmap.address_family() + self.mcast = mcast + self.host = (family=="inet6" and "FF02::C" or "239.255.255.250") + self.port = 3702 + end, - --- Sets the timeout for socket reads - setTimeout = function( self, timeout ) self.timeout = timeout end, + --- Sets the timeout for socket reads + setTimeout = function( self, timeout ) self.timeout = timeout end, - --- Sends a probe, receives and decodes a probematch - -- - -- @param probename string containing the name of the probe to send - -- check probes for available probes - -- @return status true on success, false on failure - -- @return matches table containing responses, suitable for printing using - -- the stdnse.format_output function - discoverServices = function( self, probename ) - if ( not(HAVE_SSL) ) then return false, "The wsdd library requires OpenSSL" end + --- Sends a probe, receives and decodes a probematch + -- + -- @param probename string containing the name of the probe to send + -- check probes for available probes + -- @return status true on success, false on failure + -- @return matches table containing responses, suitable for printing using + -- the stdnse.format_output function + discoverServices = function( self, probename ) + if ( not(HAVE_SSL) ) then return false, "The wsdd library requires OpenSSL" end - local comm = Comm:new(self.host, self.port, self.mcast) - local probe = Util.getProbeByName(probename) - comm:setProbe( probe ) - comm:setTimeout( self.timeout ) + local comm = Comm:new(self.host, self.port, self.mcast) + local probe = Util.getProbeByName(probename) + comm:setProbe( probe ) + comm:setTimeout( self.timeout ) - local status = comm:sendProbe() - if ( not(status) ) then - return false, "ERROR: wcf.discoverServices failed" - end + local status = comm:sendProbe() + if ( not(status) ) then + return false, "ERROR: wcf.discoverServices failed" + end - local status, matches = comm:recvProbeMatches() - if ( not(status) ) then - return false, "ERROR: wcf.recvProbeMatches failed" - end + local status, matches = comm:recvProbeMatches() + if ( not(status) ) then + return false, "ERROR: wcf.recvProbeMatches failed" + end - if ( #matches > 0 ) then matches.name = probe.desc end - return true, matches - end, + if ( #matches > 0 ) then matches.name = probe.desc end + return true, matches + end, - --- Sends a general probe to attempt to discover WSDD supporting devices - -- - -- @return status true on success, false on failure - -- @return matches table containing responses, suitable for printing using - -- the stdnse.format_output function - discoverDevices = function( self ) - return self:discoverServices('general') - end, + --- Sends a general probe to attempt to discover WSDD supporting devices + -- + -- @return status true on success, false on failure + -- @return matches table containing responses, suitable for printing using + -- the stdnse.format_output function + discoverDevices = function( self ) + return self:discoverServices('general') + end, - --- Sends a probe that attempts to discover WCF web services - -- - -- @return status true on success, false on failure - -- @return matches table containing responses, suitable for printing using - -- the stdnse.format_output function - discoverWCFServices = function( self ) - return self:discoverServices('wcf') - end, + --- Sends a probe that attempts to discover WCF web services + -- + -- @return status true on success, false on failure + -- @return matches table containing responses, suitable for printing using + -- the stdnse.format_output function + discoverWCFServices = function( self ) + return self:discoverServices('wcf') + end, } diff --git a/nselib/xdmcp.lua b/nselib/xdmcp.lua index bef87ce0d..1b4c89420 100644 --- a/nselib/xdmcp.lua +++ b/nselib/xdmcp.lua @@ -13,388 +13,388 @@ _ENV = stdnse.module("xdmcp", stdnse.seeall) -- Supported operations OpCode = { - BCAST_QUERY = 1, - QUERY = 2, - WILLING = 5, - REQUEST = 7, - ACCEPT = 8, - MANAGE = 10, + BCAST_QUERY = 1, + QUERY = 2, + WILLING = 5, + REQUEST = 7, + ACCEPT = 8, + MANAGE = 10, } -- Packet class Packet = { - -- The cdmcp header - Header = { + -- The cdmcp header + Header = { - -- Creates a new instance of class - -- @param version number containing the protocol version - -- @param opcode number containing the opcode type - -- @param length number containing the length of the data - -- @return o instance of class - new = function(self, version, opcode, length) - local o = { version = version, opcode = opcode, length = length } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @param version number containing the protocol version + -- @param opcode number containing the opcode type + -- @param length number containing the length of the data + -- @return o instance of class + new = function(self, version, opcode, length) + local o = { version = version, opcode = opcode, length = length } + setmetatable(o, self) + self.__index = self + return o + end, - -- Parses data based on which a new object is instantiated - -- @param data opaque string containing data received over the wire - -- @return hdr instance of class - parse = function(data) - local pos, hdr = nil, Packet.Header:new() - pos, hdr.version, hdr.opcode, hdr.length = bin.unpack(">SSS", data) - return hdr - end, + -- Parses data based on which a new object is instantiated + -- @param data opaque string containing data received over the wire + -- @return hdr instance of class + parse = function(data) + local pos, hdr = nil, Packet.Header:new() + pos, hdr.version, hdr.opcode, hdr.length = bin.unpack(">SSS", data) + return hdr + end, - -- Converts the instance to an opaque string - -- @return str string containing the instance - __tostring = function(self) - assert(self.length, "No header length was supplied") - return bin.pack(">SSS", self.version, self.opcode, self.length) - end, - }, + -- Converts the instance to an opaque string + -- @return str string containing the instance + __tostring = function(self) + assert(self.length, "No header length was supplied") + return bin.pack(">SSS", self.version, self.opcode, self.length) + end, + }, - [OpCode.QUERY] = { + [OpCode.QUERY] = { - -- Creates a new instance of class - -- @param authnames table of strings containing authentication - -- mechanism names. - -- @return o instance of class - new = function(self, authnames) - local o = { - header = Packet.Header:new(1, OpCode.QUERY), - authnames = authnames or {}, - } - o.header.length = #o.authnames + 1 - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @param authnames table of strings containing authentication + -- mechanism names. + -- @return o instance of class + new = function(self, authnames) + local o = { + header = Packet.Header:new(1, OpCode.QUERY), + authnames = authnames or {}, + } + o.header.length = #o.authnames + 1 + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the instance to an opaque string - -- @return str string containing the instance - __tostring = function(self) - local data = tostring(self.header) - data = data .. bin.pack("C", #self.authnames) - for _, name in ipairs(self.authnames) do - data = data .. bin.pack("P", name) - end - return data - end, + -- Converts the instance to an opaque string + -- @return str string containing the instance + __tostring = function(self) + local data = tostring(self.header) + data = data .. bin.pack("C", #self.authnames) + for _, name in ipairs(self.authnames) do + data = data .. bin.pack("P", name) + end + return data + end, - }, + }, - [OpCode.BCAST_QUERY] = { - new = function(...) - local packet = Packet[OpCode.QUERY]:new(...) - packet.header.opcode = OpCode.BCAST_QUERY - return packet - end, + [OpCode.BCAST_QUERY] = { + new = function(...) + local packet = Packet[OpCode.QUERY]:new(...) + packet.header.opcode = OpCode.BCAST_QUERY + return packet + end, - __tostring = function(...) - return Packet[OpCode.QUERY]:__tostring(...) - end + __tostring = function(...) + return Packet[OpCode.QUERY]:__tostring(...) + end - }, + }, - [OpCode.WILLING] = { + [OpCode.WILLING] = { - -- Creates a new instance of class - -- @return o instance of class - new = function(self) - local o = { - header = Packet.Header:new(1, OpCode.WILLING) - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @return o instance of class + new = function(self) + local o = { + header = Packet.Header:new(1, OpCode.WILLING) + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Parses data based on which a new object is instantiated - -- @param data opaque string containing data received over the wire - -- @return hdr instance of class - parse = function(data) - local willing = Packet[OpCode.WILLING]:new() - willing.header = Packet.Header.parse(data) + -- Parses data based on which a new object is instantiated + -- @param data opaque string containing data received over the wire + -- @return hdr instance of class + parse = function(data) + local willing = Packet[OpCode.WILLING]:new() + willing.header = Packet.Header.parse(data) - local pos = 7 - pos, willing.authname, willing.hostname, - willing.status = bin.unpack("ppp", data, pos) - return willing - end, + local pos = 7 + pos, willing.authname, willing.hostname, + willing.status = bin.unpack("ppp", data, pos) + return willing + end, - }, + }, - [OpCode.REQUEST] = { + [OpCode.REQUEST] = { - -- The connection class - Connection = { + -- The connection class + Connection = { - IpType = { - IPv4 = 0, - IPv6 = 6, - }, + IpType = { + IPv4 = 0, + IPv6 = 6, + }, - -- Creates a new instance of class - -- @param iptype number - -- @param ip opaque string containing the ip - -- @return o instance of class - new = function(self, iptype, ip) - local o = { - iptype = iptype, - ip = ip, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @param iptype number + -- @param ip opaque string containing the ip + -- @return o instance of class + new = function(self, iptype, ip) + local o = { + iptype = iptype, + ip = ip, + } + setmetatable(o, self) + self.__index = self + return o + end, - }, + }, - -- Creates a new instance of class - -- @param disp_no number containing the display name - -- @param auth_name string containing the authentication name - -- @param auth_data string containing additional authentication data - -- @param authr_names string containing authorization mechanisms - -- @param manf_id string containing the manufacturer id - -- @return o instance of class - new = function(self, disp_no, conns, auth_name, auth_data, authr_names, manf_id ) - local o = { - header = Packet.Header:new(1, OpCode.REQUEST), - disp_no = disp_no or 1, - conns = conns or {}, - auth_name = auth_name or "", - auth_data = auth_data or "", - authr_names = authr_names or {}, - manf_id = manf_id or "", - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @param disp_no number containing the display name + -- @param auth_name string containing the authentication name + -- @param auth_data string containing additional authentication data + -- @param authr_names string containing authorization mechanisms + -- @param manf_id string containing the manufacturer id + -- @return o instance of class + new = function(self, disp_no, conns, auth_name, auth_data, authr_names, manf_id ) + local o = { + header = Packet.Header:new(1, OpCode.REQUEST), + disp_no = disp_no or 1, + conns = conns or {}, + auth_name = auth_name or "", + auth_data = auth_data or "", + authr_names = authr_names or {}, + manf_id = manf_id or "", + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Adds a new connection entry - -- @param conn instance of Connections - addConnection = function(self, conn) - table.insert(self.conns, conn) - end, + -- Adds a new connection entry + -- @param conn instance of Connections + addConnection = function(self, conn) + table.insert(self.conns, conn) + end, - -- Adds a new authorization entry - -- @param str string containing the name of the authorization mechanism - addAuthrName = function(self, str) - table.insert(self.authr_names, str) - end, + -- Adds a new authorization entry + -- @param str string containing the name of the authorization mechanism + addAuthrName = function(self, str) + table.insert(self.authr_names, str) + end, - -- Converts the instance to an opaque string - -- @return str string containing the instance - __tostring = function(self) - local data = bin.pack(">SC", self.disp_no, #self.conns) - for _, conn in ipairs(self.conns) do - data = data .. bin.pack(">S", conn.iptype) - end - data = data .. bin.pack("C", #self.conns) - for _, conn in ipairs(self.conns) do - data = data .. bin.pack(">P", ipOps.ip_to_str(conn.ip)) - end - data = data .. bin.pack(">PP", self.auth_name, self.auth_data) - data = data .. bin.pack("C", #self.authr_names) - for _, authr in ipairs(self.authr_names) do - data = data .. bin.pack(">P", authr) - end - data = data .. bin.pack(">P", self.manf_id) - self.header.length = #data + -- Converts the instance to an opaque string + -- @return str string containing the instance + __tostring = function(self) + local data = bin.pack(">SC", self.disp_no, #self.conns) + for _, conn in ipairs(self.conns) do + data = data .. bin.pack(">S", conn.iptype) + end + data = data .. bin.pack("C", #self.conns) + for _, conn in ipairs(self.conns) do + data = data .. bin.pack(">P", ipOps.ip_to_str(conn.ip)) + end + data = data .. bin.pack(">PP", self.auth_name, self.auth_data) + data = data .. bin.pack("C", #self.authr_names) + for _, authr in ipairs(self.authr_names) do + data = data .. bin.pack(">P", authr) + end + data = data .. bin.pack(">P", self.manf_id) + self.header.length = #data - return tostring(self.header) .. data - end, + return tostring(self.header) .. data + end, - }, + }, - [OpCode.ACCEPT] = { + [OpCode.ACCEPT] = { - -- Creates a new instance of class - -- @param session_id number containing the session id - -- @param auth_name string containing the authentication name - -- @param auth_data string containing additional authentication data - -- @param authr_name string containing the authorization mechanism name - -- @param authr_names string containing authorization mechanisms - -- @return o instance of class - new = function(self, session_id, auth_name, auth_data, authr_name, authr_data) - local o = { - header = Packet.Header:new(1, OpCode.ACCEPT), - session_id = session_id, - auth_name = auth_name, - auth_data = auth_data, - authr_name = authr_name, - authr_data = authr_data, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @param session_id number containing the session id + -- @param auth_name string containing the authentication name + -- @param auth_data string containing additional authentication data + -- @param authr_name string containing the authorization mechanism name + -- @param authr_names string containing authorization mechanisms + -- @return o instance of class + new = function(self, session_id, auth_name, auth_data, authr_name, authr_data) + local o = { + header = Packet.Header:new(1, OpCode.ACCEPT), + session_id = session_id, + auth_name = auth_name, + auth_data = auth_data, + authr_name = authr_name, + authr_data = authr_data, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Parses data based on which a new object is instantiated - -- @param data opaque string containing data received over the wire - -- @return hdr instance of class - parse = function(data) - local accept = Packet[OpCode.ACCEPT]:new() - accept.header = Packet.Header.parse(data) - local pos = 7 - pos, accept.session_id, accept.auth_name, accept.auth_data, - accept.authr_name, accept.authr_data = bin.unpack(">IPPPP", data, pos) - return accept - end, + -- Parses data based on which a new object is instantiated + -- @param data opaque string containing data received over the wire + -- @return hdr instance of class + parse = function(data) + local accept = Packet[OpCode.ACCEPT]:new() + accept.header = Packet.Header.parse(data) + local pos = 7 + pos, accept.session_id, accept.auth_name, accept.auth_data, + accept.authr_name, accept.authr_data = bin.unpack(">IPPPP", data, pos) + return accept + end, - }, + }, - [OpCode.MANAGE] = { + [OpCode.MANAGE] = { - -- Creates a new instance of class - -- @param session_id number containing the session id - -- @param disp_no number containing the display number - -- @param disp_class string containing the display class - -- @return o instance of class - new = function(self, sess_id, disp_no, disp_class) - local o = { - header = Packet.Header:new(1, OpCode.MANAGE), - session_id = sess_id, - disp_no = disp_no, - disp_class = disp_class or "" - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of class + -- @param session_id number containing the session id + -- @param disp_no number containing the display number + -- @param disp_class string containing the display class + -- @return o instance of class + new = function(self, sess_id, disp_no, disp_class) + local o = { + header = Packet.Header:new(1, OpCode.MANAGE), + session_id = sess_id, + disp_no = disp_no, + disp_class = disp_class or "" + } + setmetatable(o, self) + self.__index = self + return o + end, - -- Converts the instance to an opaque string - -- @return str string containing the instance - __tostring = function(self) - local data = bin.pack(">ISP", self.session_id, self.disp_no, self.disp_class) - self.header.length = #data - return tostring(self.header) .. data - end, + -- Converts the instance to an opaque string + -- @return str string containing the instance + __tostring = function(self) + local data = bin.pack(">ISP", self.session_id, self.disp_no, self.disp_class) + self.header.length = #data + return tostring(self.header) .. data + end, - } + } } -- The Helper class serves as the main script interface Helper = { - -- Creates a new instance of Helper - -- @param host table as received by the action method - -- @param port table as received by the action method - -- @param options table - -- @retun o new instance of Helper - new = function(self, host, port, options) - local o = { - host = host, - port = port, - options = options or {}, - } - setmetatable(o, self) - self.__index = self - return o - end, + -- Creates a new instance of Helper + -- @param host table as received by the action method + -- @param port table as received by the action method + -- @param options table + -- @retun o new instance of Helper + new = function(self, host, port, options) + local o = { + host = host, + port = port, + options = options or {}, + } + setmetatable(o, self) + self.__index = self + return o + end, - -- "Connects" to the server (ie. creates the socket) - -- @return status, true on success, false on failure - connect = function(self) - self.socket = nmap.new_socket("udp") - self.socket:set_timeout(self.options.timeout or 10000) - return true - end, + -- "Connects" to the server (ie. creates the socket) + -- @return status, true on success, false on failure + connect = function(self) + self.socket = nmap.new_socket("udp") + self.socket:set_timeout(self.options.timeout or 10000) + return true + end, - -- Creates a xdmcp session - -- @param auth_name string containing the authentication name - -- @param authr_name string containing the authorization mechanism name - -- @param disp_class string containing the display class - -- @return status true on success, false on failure - -- @return response table or err string containing an error message - createSession = function(self, auth_names, authr_names, disp_no) - local info = nmap.get_interface_info(self.host.interface) - if ( not(info) ) then - return false, ("Failed to get information for interface %s"):format(self.host.interface) - end + -- Creates a xdmcp session + -- @param auth_name string containing the authentication name + -- @param authr_name string containing the authorization mechanism name + -- @param disp_class string containing the display class + -- @return status true on success, false on failure + -- @return response table or err string containing an error message + createSession = function(self, auth_names, authr_names, disp_no) + local info = nmap.get_interface_info(self.host.interface) + if ( not(info) ) then + return false, ("Failed to get information for interface %s"):format(self.host.interface) + end - local req = Packet[OpCode.QUERY]:new(auth_names) - local status, response = self:exch(req) - if ( not(status) ) then - return false, response - elseif ( response.header.opcode ~= OpCode.WILLING ) then - return false, "Received unexpected response" - end + local req = Packet[OpCode.QUERY]:new(auth_names) + local status, response = self:exch(req) + if ( not(status) ) then + return false, response + elseif ( response.header.opcode ~= OpCode.WILLING ) then + return false, "Received unexpected response" + end - local REQ = Packet[OpCode.REQUEST] - local iptype = REQ.Connection.IpType.IPv4 - if ( nmap.address_family() == 'inet6' ) then - iptype = REQ.Connection.IpType.IPv6 - end + local REQ = Packet[OpCode.REQUEST] + local iptype = REQ.Connection.IpType.IPv4 + if ( nmap.address_family() == 'inet6' ) then + iptype = REQ.Connection.IpType.IPv6 + end - local conns = { REQ.Connection:new(iptype, info.address) } - local req = REQ:new(disp_no, conns, nil, nil, authr_names) - local status, response = self:exch(req) - if ( not(status) ) then - return false, response - elseif ( response.header.opcode ~= OpCode.ACCEPT ) then - return false, "Received unexpected response" - end + local conns = { REQ.Connection:new(iptype, info.address) } + local req = REQ:new(disp_no, conns, nil, nil, authr_names) + local status, response = self:exch(req) + if ( not(status) ) then + return false, response + elseif ( response.header.opcode ~= OpCode.ACCEPT ) then + return false, "Received unexpected response" + end - -- Sending this last manage packet doesn't make any sense as we can't - -- set up a listening TCP server anyway. When we can, we could enable - -- this and wait for the incoming request and retrieve X protocol info. + -- Sending this last manage packet doesn't make any sense as we can't + -- set up a listening TCP server anyway. When we can, we could enable + -- this and wait for the incoming request and retrieve X protocol info. - -- local manage = Packet[OpCode.MANAGE]:new(response.session_id, - -- disp_no, "MIT-unspecified") - -- local status, response = self:exch(manage) - -- if ( not(status) ) then - -- return false, response - -- end + -- local manage = Packet[OpCode.MANAGE]:new(response.session_id, + -- disp_no, "MIT-unspecified") + -- local status, response = self:exch(manage) + -- if ( not(status) ) then + -- return false, response + -- end - return true, { - session_id = response.session_id, - auth_name = response.auth_name, - auth_data = response.auth_data, - authr_name = response.authr_name, - authr_data = response.authr_data, - } - end, + return true, { + session_id = response.session_id, + auth_name = response.auth_name, + auth_data = response.auth_data, + authr_name = response.authr_name, + authr_data = response.authr_data, + } + end, - send = function(self, req) - return self.socket:sendto(self.host, self.port, tostring(req)) - end, + send = function(self, req) + return self.socket:sendto(self.host, self.port, tostring(req)) + end, - recv = function(self) - local status, data = self.socket:receive() - if ( not(status) ) then - return false, data - end - local header = Packet.Header.parse(data) - if ( not(header) ) then - return false, "Failed to parse xdmcp header" - end - if ( not(Packet[header.opcode]) ) then - return false, ("No parser for opcode: %d"):format(header.opcode) - end - local resp = Packet[header.opcode].parse(data) - if ( not(resp) ) then - return false, "Failed to parse response" - end - return true, resp - end, + recv = function(self) + local status, data = self.socket:receive() + if ( not(status) ) then + return false, data + end + local header = Packet.Header.parse(data) + if ( not(header) ) then + return false, "Failed to parse xdmcp header" + end + if ( not(Packet[header.opcode]) ) then + return false, ("No parser for opcode: %d"):format(header.opcode) + end + local resp = Packet[header.opcode].parse(data) + if ( not(resp) ) then + return false, "Failed to parse response" + end + return true, resp + end, - -- Sends a request to the server, receives and parses a response - -- @param req instance of Packet - -- @return status true on success, false on failure - -- @return response instance of response packet - exch = function(self, req) - local status, err = self:send(req) - if ( not(status) ) then - return false, "Failed to send xdmcp request" - end - return self:recv() - end, + -- Sends a request to the server, receives and parses a response + -- @param req instance of Packet + -- @return status true on success, false on failure + -- @return response instance of response packet + exch = function(self, req) + local status, err = self:send(req) + if ( not(status) ) then + return false, "Failed to send xdmcp request" + end + return self:recv() + end, } diff --git a/nselib/xmpp.lua b/nselib/xmpp.lua index 6deedf376..e47e63f08 100644 --- a/nselib/xmpp.lua +++ b/nselib/xmpp.lua @@ -7,20 +7,20 @@ -- script written by Vasiliy Kulikov. -- -- The library consist of the following classes: --- * XML - containing a minimal XML parser written by --- Vasiliy Kulikov. --- * TagProcessor - Contains processing code for common tags --- * XMPP - containing the low-level functions used to --- communicate with the Jabber server. --- * Helper - containing the main interface for script --- writers +-- * XML - containing a minimal XML parser written by +-- Vasiliy Kulikov. +-- * TagProcessor - Contains processing code for common tags +-- * XMPP - containing the low-level functions used to +-- communicate with the Jabber server. +-- * Helper - containing the main interface for script +-- writers -- -- The following sample illustrates how to use the library to authenticate -- to a XMPP sever: -- --- local helper = xmpp.Helper:new(host, port, options) --- local status, err = helper:connect() --- status, err = helper:login(user, pass, "DIGEST-MD5") +-- local helper = xmpp.Helper:new(host, port, options) +-- local status, err = helper:connect() +-- status, err = helper:login(user, pass, "DIGEST-MD5") -- -- -- @copyright Same as Nmap--See http://nmap.org/book/man-legal.html @@ -29,7 +29,7 @@ -- Version 0.2 -- Created 07/19/2011 - v0.1 - Created by Patrik Karlsson -- Revised 07/22/2011 - v0.2 - Added TagProcessors and two new auth mechs: --- CRAM-MD5 and LOGIN +-- CRAM-MD5 and LOGIN local base64 = require "base64" local nmap = require "nmap" @@ -42,381 +42,381 @@ _ENV = stdnse.module("xmpp", stdnse.seeall) XML = { - -- This is a trivial XML processor written by Vasiliy Kulikov. It doesn't - -- fully support XML, but it should be sufficient for the basic XMPP - -- stream handshake. If you see stanzas with uncommon symbols, feel - -- free to enhance these regexps. - parse_tag = function(s) - local _, _, contents, empty, name = string.find(s, "([^<]*)<(/?)([?:%w-]+)") - local attrs = {} - if not name then - return - end - for k, v in string.gmatch(s, "%s([%w:]+)='([^']+)'") do - attrs[k] = v - end - for k, v in string.gmatch(s, "%s([%w:]+)=\"([^\"]+)\"") do - attrs[k] = v - end + -- This is a trivial XML processor written by Vasiliy Kulikov. It doesn't + -- fully support XML, but it should be sufficient for the basic XMPP + -- stream handshake. If you see stanzas with uncommon symbols, feel + -- free to enhance these regexps. + parse_tag = function(s) + local _, _, contents, empty, name = string.find(s, "([^<]*)<(/?)([?:%w-]+)") + local attrs = {} + if not name then + return + end + for k, v in string.gmatch(s, "%s([%w:]+)='([^']+)'") do + attrs[k] = v + end + for k, v in string.gmatch(s, "%s([%w:]+)=\"([^\"]+)\"") do + attrs[k] = v + end - local finish = (empty ~= "") or (s:sub(#s-1) == '/>') + local finish = (empty ~= "") or (s:sub(#s-1) == '/>') - return { name = name, - attrs = attrs, - start = (empty == ""), - contents = contents, - finish = finish } - end, + return { name = name, + attrs = attrs, + start = (empty == ""), + contents = contents, + finish = finish } + end, } TagProcessor = { - ["failure"] = function(socket, tag) - return TagProcessor["success"](socket,tag) - end, + ["failure"] = function(socket, tag) + return TagProcessor["success"](socket,tag) + end, - ["success"] = function(socket, tag) - if ( tag.finish ) then return true end - local newtag - repeat - local status, data = socket:receive_buf(">", true) - if ( not(status) ) then - return false, ("ERROR: Failed to process %s tag"):format(tag.name) - end - newtag = XML.parse_tag(data) - until( newtag.finish and newtag.name == tag.name ) - if ( newtag.name == tag.name ) then return true, tag end - return false, ("ERROR: Failed to process %s tag"):format(tag.name) - end, + ["success"] = function(socket, tag) + if ( tag.finish ) then return true end + local newtag + repeat + local status, data = socket:receive_buf(">", true) + if ( not(status) ) then + return false, ("ERROR: Failed to process %s tag"):format(tag.name) + end + newtag = XML.parse_tag(data) + until( newtag.finish and newtag.name == tag.name ) + if ( newtag.name == tag.name ) then return true, tag end + return false, ("ERROR: Failed to process %s tag"):format(tag.name) + end, - ["challenge"] = function(socket, tag) - local status, data = socket:receive_buf(">", true) - if ( not(status) ) then return false, "ERROR: Failed to read challenge tag" end - local tag = XML.parse_tag(data) + ["challenge"] = function(socket, tag) + local status, data = socket:receive_buf(">", true) + if ( not(status) ) then return false, "ERROR: Failed to read challenge tag" end + local tag = XML.parse_tag(data) - if ( not(status) or tag.name ~= "challenge" ) then - return false, "ERROR: Failed to process challenge" - end - return status, (tag.contents and base64.dec(tag.contents)) - end, + if ( not(status) or tag.name ~= "challenge" ) then + return false, "ERROR: Failed to process challenge" + end + return status, (tag.contents and base64.dec(tag.contents)) + end, } XMPP = { - --- Creates a new instance of the XMPP class - -- - -- @param host table as receieved by the action function - -- @param port table as receieved by the action function - -- @param options table containing options, currently supported - -- timeout - sets the socket timeout - -- servername - sets the server name to use in - -- communication with the server. - -- starttls - start TLS handshake even if it is optional. - new = function(self, host, port, options) - local o = { host = host, - port = port, - options = options or {}, - auth = { mechs = {} } } - o.options.timeout = o.options.timeout and o.options.timeout or 10 - o.servername = stdnse.get_hostname(host) or o.options.servername - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new instance of the XMPP class + -- + -- @param host table as receieved by the action function + -- @param port table as receieved by the action function + -- @param options table containing options, currently supported + -- timeout - sets the socket timeout + -- servername - sets the server name to use in + -- communication with the server. + -- starttls - start TLS handshake even if it is optional. + new = function(self, host, port, options) + local o = { host = host, + port = port, + options = options or {}, + auth = { mechs = {} } } + o.options.timeout = o.options.timeout and o.options.timeout or 10 + o.servername = stdnse.get_hostname(host) or o.options.servername + setmetatable(o, self) + self.__index = self + return o + end, - --- Sends data to XMPP server - -- @param data string containing data to send to server - -- @return status true on success false on failure - -- @return err string containing error message - send = function(self, data) + --- Sends data to XMPP server + -- @param data string containing data to send to server + -- @return status true on success false on failure + -- @return err string containing error message + send = function(self, data) - -- this ain't pretty, but we try to "flush" what's left of the receive - -- buffer, prior to send. This way we account for not reading to the - -- end of one message resulting in the next read reading from our - -- previous message. - self.socket:set_timeout(1) - repeat - local status = self.socket:receive_buf("\0", false) - until(not(status)) - self.socket:set_timeout(self.options.timeout * 1000) + -- this ain't pretty, but we try to "flush" what's left of the receive + -- buffer, prior to send. This way we account for not reading to the + -- end of one message resulting in the next read reading from our + -- previous message. + self.socket:set_timeout(1) + repeat + local status = self.socket:receive_buf("\0", false) + until(not(status)) + self.socket:set_timeout(self.options.timeout * 1000) - return self.socket:send(data) - end, + return self.socket:send(data) + end, - --- Receives a XML tag from the server - -- - -- @param tag [optional] if unset, receives the next available tag - -- if set, reads until the given tag has been found - -- @param close [optional] if set, matches a closing tag - receive_tag = function(self, tag, close) - local result - repeat - local status, data = self.socket:receive_buf(">", true) - if ( not(status) ) then return false, data end - result = XML.parse_tag(data) - until( ( not(tag) and (close == nil or result.finish == close ) ) or - ( tag == result.name and ( close == nil or result.finish == close ) ) ) - return true, result - end, + --- Receives a XML tag from the server + -- + -- @param tag [optional] if unset, receives the next available tag + -- if set, reads until the given tag has been found + -- @param close [optional] if set, matches a closing tag + receive_tag = function(self, tag, close) + local result + repeat + local status, data = self.socket:receive_buf(">", true) + if ( not(status) ) then return false, data end + result = XML.parse_tag(data) + until( ( not(tag) and (close == nil or result.finish == close ) ) or + ( tag == result.name and ( close == nil or result.finish == close ) ) ) + return true, result + end, - --- Connects to the XMPP server - -- @return status true on success, false on failure - -- @return err string containing an error message if status is false - connect = function(self) - assert(self.servername, - "Cannot connect to XMPP server without valid server name") + --- Connects to the XMPP server + -- @return status true on success, false on failure + -- @return err string containing an error message if status is false + connect = function(self) + assert(self.servername, + "Cannot connect to XMPP server without valid server name") - -- we may be reconnecting using SSL - if ( not(self.socket) ) then - self.socket = nmap.new_socket() - self.socket:set_timeout(self.options.timeout * 1000) - local status, err = self.socket:connect(self.host, self.port) - if ( not(status) ) then - return false, err - end - end - local data = (""):format(self.servername) + -- we may be reconnecting using SSL + if ( not(self.socket) ) then + self.socket = nmap.new_socket() + self.socket:set_timeout(self.options.timeout * 1000) + local status, err = self.socket:connect(self.host, self.port) + if ( not(status) ) then + return false, err + end + end + local data = (""):format(self.servername) - local status, err = self:send(data) - if ( not(status) ) then return false, "ERROR: Failed to connect to server" end + local status, err = self:send(data) + if ( not(status) ) then return false, "ERROR: Failed to connect to server" end - local version, start_tls - repeat - local status, tag = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to connect to server" end + local version, start_tls + repeat + local status, tag = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to connect to server" end - if ( tag.name == "stream:stream" ) then - version = tag.attrs and tag.attrs.version - elseif ( tag.name == "starttls" and tag.start ) then - status, tag = self:receive_tag() - if ( not(status) ) then - return false, "ERROR: Failed to connect to server" - end - if ( tag.name ~= "starttls" ) then - start_tls = tag.name - else - start_tls = "optional" - end - elseif ( tag.name == "mechanism" and tag.finish ) then - self.auth.mechs[tag.contents] = true - end - until(tag.name == "stream:features" and tag.finish) + if ( tag.name == "stream:stream" ) then + version = tag.attrs and tag.attrs.version + elseif ( tag.name == "starttls" and tag.start ) then + status, tag = self:receive_tag() + if ( not(status) ) then + return false, "ERROR: Failed to connect to server" + end + if ( tag.name ~= "starttls" ) then + start_tls = tag.name + else + start_tls = "optional" + end + elseif ( tag.name == "mechanism" and tag.finish ) then + self.auth.mechs[tag.contents] = true + end + until(tag.name == "stream:features" and tag.finish) - if ( version ~= "1.0" ) then - return false, "ERROR: Only version 1.0 is supported" - end + if ( version ~= "1.0" ) then + return false, "ERROR: Only version 1.0 is supported" + end - if ( start_tls == "required" or self.options.starttls) then - status, err = self:send("") - if ( not(status) ) then return false, "ERROR: Failed to initiate STARTTLS" end - local status, tag = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to recevice from server" end - if ( tag.name == "proceed" ) then - status, err = self.socket:reconnect_ssl() - self.options.starttls = false - return self:connect() - end - end + if ( start_tls == "required" or self.options.starttls) then + status, err = self:send("") + if ( not(status) ) then return false, "ERROR: Failed to initiate STARTTLS" end + local status, tag = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to recevice from server" end + if ( tag.name == "proceed" ) then + status, err = self.socket:reconnect_ssl() + self.options.starttls = false + return self:connect() + end + end - return true - end, + return true + end, - --- Logs in to the XMPP server - -- - -- @param username string - -- @param password string - -- @param mech string containing a supported authentication mechanism - -- @return status true on success, false on failure - -- @return err string containing error message if status is false - login = function(self, username, password, mech) - assert(mech == "PLAIN" or - mech == "DIGEST-MD5" or - mech == "CRAM-MD5" or - mech == "LOGIN", - "Unsupported authentication mechanism") + --- Logs in to the XMPP server + -- + -- @param username string + -- @param password string + -- @param mech string containing a supported authentication mechanism + -- @return status true on success, false on failure + -- @return err string containing error message if status is false + login = function(self, username, password, mech) + assert(mech == "PLAIN" or + mech == "DIGEST-MD5" or + mech == "CRAM-MD5" or + mech == "LOGIN", + "Unsupported authentication mechanism") - local auth = (""):format(mech) + local auth = (""):format(mech) - -- we currently don't do anything with the realm - local realm + -- we currently don't do anything with the realm + local realm - -- we need to cut the @domain.tld from the username - if ( username:match("@") ) then - username, realm = username:match("^(.*)@(.*)$") - end + -- we need to cut the @domain.tld from the username + if ( username:match("@") ) then + username, realm = username:match("^(.*)@(.*)$") + end - local status, result + local status, result - if ( mech == "PLAIN" ) then - local mech_params = { username, password } - local auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params)) - auth = ("%s"):format(mech, base64.enc(auth_data)) + if ( mech == "PLAIN" ) then + local mech_params = { username, password } + local auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params)) + auth = ("%s"):format(mech, base64.enc(auth_data)) - status, result = self.socket:send(auth) - if ( not(status) ) then return false, "ERROR: Failed to send SASL PLAIN authentication" end + status, result = self.socket:send(auth) + if ( not(status) ) then return false, "ERROR: Failed to send SASL PLAIN authentication" end - status, result = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to receive login response" end + status, result = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to receive login response" end - if ( result.name == "failure" ) then - status = TagProcessor[result.name](self.socket, result) - end - else - local status, err = self.socket:send(auth) - if(not(status)) then return false, "ERROR: Failed to initiate SASL login" end + if ( result.name == "failure" ) then + status = TagProcessor[result.name](self.socket, result) + end + else + local status, err = self.socket:send(auth) + if(not(status)) then return false, "ERROR: Failed to initiate SASL login" end - local chall - status, result = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to retrieve challenge" end - status, chall = TagProcessor[result.name](self.socket, result) + local chall + status, result = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to retrieve challenge" end + status, chall = TagProcessor[result.name](self.socket, result) - if ( mech == "LOGIN" ) then - if ( chall ~= "User Name" ) then - return false, ("ERROR: Login expected 'User Name' received: %s"):format(chall) - end - self.socket:send("" .. - base64.enc(username) .. - "") + if ( mech == "LOGIN" ) then + if ( chall ~= "User Name" ) then + return false, ("ERROR: Login expected 'User Name' received: %s"):format(chall) + end + self.socket:send("" .. + base64.enc(username) .. + "") - status, result = self:receive_tag() - if ( not(status) or result.name ~= "challenge") then - return false, "ERROR: Receiving tag from server" - end - status, chall = TagProcessor[result.name](self.socket, result) + status, result = self:receive_tag() + if ( not(status) or result.name ~= "challenge") then + return false, "ERROR: Receiving tag from server" + end + status, chall = TagProcessor[result.name](self.socket, result) - if ( chall ~= "Password" ) then - return false, ("ERROR: Login expected 'Password' received: %s"):format(chall) - end + if ( chall ~= "Password" ) then + return false, ("ERROR: Login expected 'Password' received: %s"):format(chall) + end - self.socket:send("" .. - base64.enc(password) .. - "") + self.socket:send("" .. + base64.enc(password) .. + "") - status, result = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to receive login challenge" end - if ( result.name == "failure" ) then - status = TagProcessor[result.name](self.socket, result) - return false, "Login failed" - end - else - local mech_params = { username, password, chall, "xmpp", "xmpp/" .. self.servername } - local auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params)) - auth_data = "" .. - base64.enc(auth_data) .. "" + status, result = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to receive login challenge" end + if ( result.name == "failure" ) then + status = TagProcessor[result.name](self.socket, result) + return false, "Login failed" + end + else + local mech_params = { username, password, chall, "xmpp", "xmpp/" .. self.servername } + local auth_data = sasl.Helper:new(mech):encode(table.unpack(mech_params)) + auth_data = "" .. + base64.enc(auth_data) .. "" - status, err = self.socket:send(auth_data) + status, err = self.socket:send(auth_data) - -- read to the end tag regardless of what it is - -- it should be one of either: success, challenge or error - repeat - status, result = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to receive login challenge" end + -- read to the end tag regardless of what it is + -- it should be one of either: success, challenge or error + repeat + status, result = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to receive login challenge" end - if ( result.name == "failure" ) then - status = TagProcessor[result.name](self.socket, result) - return false, "Login failed" - elseif ( result.name == "success" ) then - status = TagProcessor[result.name](self.socket, result) - if ( not(status) ) then return false, "Failed to process success message" end - return true, "Login success" - elseif ( result.name ~= "challenge" ) then - return false, "ERROR: Failed to receive login challenge" - end - until( result.name == "challenge" and result.finish ) + if ( result.name == "failure" ) then + status = TagProcessor[result.name](self.socket, result) + return false, "Login failed" + elseif ( result.name == "success" ) then + status = TagProcessor[result.name](self.socket, result) + if ( not(status) ) then return false, "Failed to process success message" end + return true, "Login success" + elseif ( result.name ~= "challenge" ) then + return false, "ERROR: Failed to receive login challenge" + end + until( result.name == "challenge" and result.finish ) - if ( result.name == "challenge" and mech == "DIGEST-MD5" ) then - status, result = self.socket:send("") - if ( not(status) ) then return false, "ERROR: Failed to send DIGEST-MD5 request" end - status, result = self:receive_tag() - if ( not(status) ) then return false, "ERROR: Failed to receive DIGEST-MD5 response" end - end - end - end - if ( result.name == "success" ) then - return true, "Login success" - end + if ( result.name == "challenge" and mech == "DIGEST-MD5" ) then + status, result = self.socket:send("") + if ( not(status) ) then return false, "ERROR: Failed to send DIGEST-MD5 request" end + status, result = self:receive_tag() + if ( not(status) ) then return false, "ERROR: Failed to receive DIGEST-MD5 response" end + end + end + end + if ( result.name == "success" ) then + return true, "Login success" + end - return false, "Login failed" - end, + return false, "Login failed" + end, - --- Retrieves the available authentication mechanisms - -- @return table containing all available authentication mechanisms - getAuthMechs = function(self) return self.auth.mechs end, + --- Retrieves the available authentication mechanisms + -- @return table containing all available authentication mechanisms + getAuthMechs = function(self) return self.auth.mechs end, - --- Disconnects the socket from the server - -- @return status true on success, false on failure - disconnect = function(self) - local status, err = self.socket:close() - self.socket = nil - return status, err - end, + --- Disconnects the socket from the server + -- @return status true on success, false on failure + disconnect = function(self) + local status, err = self.socket:close() + self.socket = nil + return status, err + end, } Helper = { - --- Creates a new Helper instance - -- @param host table as receieved by the action function - -- @param port table as receieved by the action function - -- @param options table containing options, currently supported - -- timeout - sets the socket timeout - -- servername - sets the server name to use in - -- communication with the server. - new = function(self, host, port, options) - local o = { host = host, - port = port, - options = options or {}, - xmpp = XMPP:new(host, port, options), - state = "" } - setmetatable(o, self) - self.__index = self - return o - end, + --- Creates a new Helper instance + -- @param host table as receieved by the action function + -- @param port table as receieved by the action function + -- @param options table containing options, currently supported + -- timeout - sets the socket timeout + -- servername - sets the server name to use in + -- communication with the server. + new = function(self, host, port, options) + local o = { host = host, + port = port, + options = options or {}, + xmpp = XMPP:new(host, port, options), + state = "" } + setmetatable(o, self) + self.__index = self + return o + end, - --- Connects to the XMPP server and starts the initial communication - -- @return status true on success, false on failure - -- @return err string containing an error message is status is false - connect = function(self) - if ( not(self.host.targetname) and - not(self.options.servername) ) then - return false, "ERROR: Cannot connect to XMPP server without valid server name" - end - self.state = "CONNECTED" - return self.xmpp:connect() - end, + --- Connects to the XMPP server and starts the initial communication + -- @return status true on success, false on failure + -- @return err string containing an error message is status is false + connect = function(self) + if ( not(self.host.targetname) and + not(self.options.servername) ) then + return false, "ERROR: Cannot connect to XMPP server without valid server name" + end + self.state = "CONNECTED" + return self.xmpp:connect() + end, - --- Login to the XMPP server - -- - -- @param username string - -- @param password string - -- @param mech string containing a supported authentication mechanism - -- (@see getAuthMechs) - login = function(self, username, password, mech) - return self.xmpp:login(username, password, mech) - end, + --- Login to the XMPP server + -- + -- @param username string + -- @param password string + -- @param mech string containing a supported authentication mechanism + -- (@see getAuthMechs) + login = function(self, username, password, mech) + return self.xmpp:login(username, password, mech) + end, - --- Retrieves the available authentication mechanisms - -- @return table containing all available authentication mechanisms - getAuthMechs = function(self) - if ( self.state == "CONNECTED" ) then - return self.xmpp:getAuthMechs() - end - return - end, + --- Retrieves the available authentication mechanisms + -- @return table containing all available authentication mechanisms + getAuthMechs = function(self) + if ( self.state == "CONNECTED" ) then + return self.xmpp:getAuthMechs() + end + return + end, - --- Closes the connection to the server - close = function(self) - self.xmpp:disconnect() - self.state = "DISCONNECTED" - end, + --- Closes the connection to the server + close = function(self) + self.xmpp:disconnect() + self.state = "DISCONNECTED" + end, }