mirror of
https://github.com/nmap/nmap.git
synced 2025-12-06 04:31:29 +00:00
176 lines
5.2 KiB
Lua
176 lines
5.2 KiB
Lua
---
|
|
-- A minimalist RSYNC (remote file sync) library
|
|
--
|
|
-- @author Patrik Karlsson <patrik@cqure.net>
|
|
|
|
local base64 = require "base64"
|
|
local match = require "match"
|
|
local nmap = require "nmap"
|
|
local stdnse = require "stdnse"
|
|
local string = require "string"
|
|
local table = require "table"
|
|
local openssl = stdnse.silent_require "openssl"
|
|
_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,
|
|
|
|
-- Handles send and receive of control messages
|
|
-- @param data string containing the command to send
|
|
-- @return status true on success, 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(match.pattern_limit("\n", 2048), 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, socket)
|
|
self.socket = socket or 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,
|
|
|
|
-- 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
|
|
|
|
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
|
|
|
|
if ( data == "@RSYNCD: OK" ) then
|
|
return true, "Authentication successful"
|
|
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
|
|
|
|
local modules = {}
|
|
while(true) do
|
|
status, data = self.socket:receive_buf(match.pattern_limit("\n", 2048), 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
|
|
|
|
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
|
|
|
|
local len = string.unpack("<I2", data)
|
|
status, data = self.socket:receive_buf(match.numbytes(len), false)
|
|
if ( not(status) ) then
|
|
return false, data
|
|
end
|
|
|
|
-- Parsing goes here
|
|
end,
|
|
|
|
-- Disconnects from the rsync server
|
|
-- @return status true on success, false on failure
|
|
disconnect = function(self) return self.socket:close() end,
|
|
|
|
}
|
|
|
|
return _ENV;
|