nmap/nselib/rsync.lua
nnposter 7339870858
Some checks failed
nmap multiplatform autobuilds / build (arm64, gcc, ubuntu-latest-gcc-arm64, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, freebsd-15-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, macos-15-clang, macos-15) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, macos-26-clang, macos-26) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, netbsd-10-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, openbsd-7-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, solaris-11-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (clang, ubuntu-latest-clang, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (egcc, openbsd-7-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, freebsd-15-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, netbsd-10-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, solaris-11-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (gcc, ubuntu-latest-gcc, ubuntu-latest) (push) Has been cancelled
nmap multiplatform autobuilds / build (msvc, windows-latest-msvc, windows-latest) (push) Has been cancelled
Document the rsync password algorithm
2026-09-05 01:39:01 +00:00

171 lines
5.3 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 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 cmd 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, cmd)
local status, err = self.socket:send(cmd .. "\n")
if not status then
return false, err
end
return self.socket:receive_buf(match.pattern_limit("\n", 2048), false)
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]+%f[ \0]")) ) then
return false, "Protocol error"
end
return true
end,
-- Authenticates against the rsync module. If no username is given, assume
-- no authentication is required.
-- The implemented algorithm is CSUM_MD4_OLD, which is only valid for
-- protocol version 29 or lower.
-- @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
if ( data:match("@RSYNCD: OK") ) then
return true, "No authentication was required"
end
local 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
if 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, keyed with module names
listModules = function(self)
local status, data = self.socket:send("\n")
if (not(status)) then
return false, data
end
local modules = stdnse.output_table()
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
end
local module, comment = data:match("^(.-) *\t(.*)")
modules[module or data:match("^(.-)%s*$")] = {comment=comment}
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 status
local data = ("--server\n--sender\n-rc\n.\n%s\n\n"):format(self.options.module)
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), true)
if ( not(status) ) then
return false, data
end
local len = string.unpack("<I2", data)
status, data = self.socket:receive_buf(match.numbytes(len), true)
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;