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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
|
---
-- 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), 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;
|