Skip to content

Commit 14270fe

Browse files
committed
Merge branch 'release/20111219000001' into stable
2 parents 4259168 + ed4c6de commit 14270fe

File tree

5 files changed

+596
-21
lines changed

5 files changed

+596
-21
lines changed

lib/rex/proto/tftp.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@
1010

1111
require 'rex/proto/tftp/constants'
1212
require 'rex/proto/tftp/server'
13+
require 'rex/proto/tftp/client'

lib/rex/proto/tftp/client.rb

Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
require 'rex/socket'
2+
require 'rex/proto/tftp'
3+
require 'tempfile'
4+
5+
module Rex
6+
module Proto
7+
module TFTP
8+
9+
#
10+
# TFTP Client class
11+
#
12+
# Note that TFTP has blocks, and so does Ruby. Watch out with the variable names!
13+
#
14+
# The big gotcha right now is that setting the mode between octet, netascii, or
15+
# anything else doesn't actually do anything other than declare it to the
16+
# server.
17+
#
18+
# Also, since TFTP clients act as both clients and servers, we use two
19+
# threads to handle transfers, regardless of the direction. For this reason,
20+
# the transfer actions are nonblocking; if you need to see the
21+
# results of a transfer before doing something else, check the boolean complete
22+
# attribute and any return data in the :status attribute. It's a little
23+
# weird like that.
24+
#
25+
# Finally, most (all?) clients will alter the data in netascii mode in order
26+
# to try to conform to the RFC standard for what "netascii" means, but there are
27+
# ambiguities in implementations on things like if nulls are allowed, what
28+
# to do with Unicode, and all that. For this reason, "octet" is default, and
29+
# if you want to send "netascii" data, it's on you to fix up your source data
30+
# prior to sending it.
31+
#
32+
class Client
33+
34+
attr_accessor :local_host, :local_port, :peer_host, :peer_port
35+
attr_accessor :threads, :context, :server_sock, :client_sock
36+
attr_accessor :local_file, :remote_file, :mode, :action
37+
attr_accessor :complete, :recv_tempfile, :status
38+
attr_accessor :block_size # This definitely breaks spec, should only use for fuzz/sploit.
39+
40+
# Returns an array of [code, type, msg]. Data packets
41+
# specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
42+
def parse_tftp_response(str)
43+
return nil unless str.length >= 4
44+
ret = str.unpack("nnA*")
45+
ret[2] = str[4,str.size] if ret[0] == OpData
46+
return ret
47+
end
48+
49+
def initialize(params)
50+
self.threads = []
51+
self.local_host = params["LocalHost"] || "0.0.0.0"
52+
self.local_port = params["LocalPort"] || (1025 + rand(0xffff-1025))
53+
self.peer_host = params["PeerHost"] || (raise ArgumentError, "Need a peer host.")
54+
self.peer_port = params["PeerPort"] || 69
55+
self.context = params["Context"] || {}
56+
self.local_file = params["LocalFile"]
57+
self.remote_file = params["RemoteFile"] || ::File.split(self.local_file).last
58+
self.mode = params["Mode"] || "octet"
59+
self.action = params["Action"] || (raise ArgumentError, "Need an action.")
60+
self.block_size = params["BlockSize"] || 512
61+
end
62+
63+
#
64+
# Methods for both upload and download
65+
#
66+
67+
def start_server_socket
68+
self.server_sock = Rex::Socket::Udp.create(
69+
'LocalHost' => local_host,
70+
'LocalPort' => local_port,
71+
'Context' => context
72+
)
73+
if self.server_sock and block_given?
74+
yield "Started TFTP client listener on #{local_host}:#{local_port}"
75+
end
76+
self.threads << Rex::ThreadFactory.spawn("TFTPServerMonitor", false) {
77+
if block_given?
78+
monitor_server_sock {|msg| yield msg}
79+
else
80+
monitor_server_sock
81+
end
82+
}
83+
end
84+
85+
def monitor_server_sock
86+
yield "Listening for incoming ACKs" if block_given?
87+
res = self.server_sock.recvfrom(65535)
88+
if res and res[0]
89+
code, type, data = parse_tftp_response(res[0])
90+
if code == OpAck and self.action == :upload
91+
if block_given?
92+
yield "WRQ accepted, sending the file." if type == 0
93+
send_data(res[1], res[2]) {|msg| yield msg}
94+
else
95+
send_data(res[1], res[2])
96+
end
97+
elsif code == OpData and self.action == :download
98+
if block_given?
99+
recv_data(res[1], res[2], data) {|msg| yield msg}
100+
else
101+
recv_data(res[1], res[2], data)
102+
end
103+
elsif code == OpError
104+
yield("Aborting, got error type:%d, message:'%s'" % [type, data]) if block_given?
105+
self.status = {:error => [code, type, data]}
106+
else
107+
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given?
108+
self.status = {:error => [code, type, data]}
109+
end
110+
end
111+
stop
112+
end
113+
114+
def monitor_client_sock
115+
res = self.client_sock.recvfrom(65535)
116+
if res[1] # Got a response back, so that's never good; Acks come back on server_sock.
117+
code, type, data = parse_tftp_response(res[0])
118+
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given?
119+
self.status = {:error => [code, type, data]}
120+
stop
121+
end
122+
end
123+
124+
def stop
125+
self.complete = true
126+
begin
127+
self.server_sock.close
128+
self.client_sock.close
129+
self.server_sock = nil
130+
self.client_sock = nil
131+
self.threads.each {|t| t.kill}
132+
rescue
133+
nil
134+
end
135+
end
136+
137+
#
138+
# Methods for download
139+
#
140+
141+
def rrq_packet
142+
req = [OpRead, self.remote_file, self.mode]
143+
packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}"
144+
req.pack(packstr)
145+
end
146+
147+
def ack_packet(blocknum=0)
148+
req = [OpAck, blocknum].pack("nn")
149+
end
150+
151+
def send_read_request(&block)
152+
self.status = nil
153+
self.complete = false
154+
if block_given?
155+
start_server_socket {|msg| yield msg}
156+
else
157+
start_server_socket
158+
end
159+
self.client_sock = Rex::Socket::Udp.create(
160+
'PeerHost' => peer_host,
161+
'PeerPort' => peer_port,
162+
'LocalHost' => local_host,
163+
'LocalPort' => local_port,
164+
'Context' => context
165+
)
166+
self.client_sock.sendto(rrq_packet, peer_host, peer_port)
167+
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
168+
if block_given?
169+
monitor_client_sock {|msg| yield msg}
170+
else
171+
monitor_client_sock
172+
end
173+
}
174+
until self.complete
175+
return self.status
176+
end
177+
end
178+
179+
def recv_data(host, port, first_block)
180+
self.recv_tempfile = Rex::Quickfile.new('msf-tftp')
181+
recvd_blocks = 1
182+
if block_given?
183+
yield "Source file: #{self.remote_file}, destination file: #{self.local_file}"
184+
yield "Received and acknowledged #{first_block.size} in block #{recvd_blocks}"
185+
end
186+
if block_given?
187+
write_and_ack_data(first_block,1,host,port) {|msg| yield msg}
188+
else
189+
write_and_ack_data(first_block,1,host,port)
190+
end
191+
current_block = first_block
192+
while current_block.size == 512
193+
res = self.server_sock.recvfrom(65535)
194+
if res and res[0]
195+
code, block_num, current_block = parse_tftp_response(res[0])
196+
if code == 3
197+
if block_given?
198+
write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg}
199+
else
200+
write_and_ack_data(current_block,block_num,host,port)
201+
end
202+
recvd_blocks += 1
203+
else
204+
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
205+
stop
206+
end
207+
end
208+
end
209+
if block_given?
210+
yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!")
211+
end
212+
self.status = {:success => [
213+
self.local_file,
214+
self.remote_file,
215+
self.recv_tempfile.size,
216+
recvd_blocks.size]
217+
}
218+
self.recv_tempfile.close
219+
stop
220+
end
221+
222+
def write_and_ack_data(data,blocknum,host,port)
223+
self.recv_tempfile.write(data)
224+
self.recv_tempfile.flush
225+
req = ack_packet(blocknum)
226+
self.server_sock.sendto(req, host, port)
227+
yield "Received and acknowledged #{data.size} in block #{blocknum}" if block_given?
228+
end
229+
230+
#
231+
# Methods for upload
232+
#
233+
234+
def wrq_packet
235+
req = [OpWrite, self.remote_file, self.mode]
236+
packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}"
237+
req.pack(packstr)
238+
end
239+
240+
# Note that the local filename for uploading need not be a real filename --
241+
# if it begins with DATA: it can be any old string of bytes. If it's missing
242+
# completely, then just quit.
243+
def blockify_file_or_data
244+
if self.local_file =~ /^DATA:(.*)/m
245+
data = $1
246+
elsif ::File.file?(self.local_file) and ::File.readable?(self.local_file)
247+
data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size} rescue []
248+
else
249+
return []
250+
end
251+
data_blocks = data.scan(/.{1,#{block_size}}/m)
252+
# Drop any trailing empty blocks
253+
if data_blocks.size > 1 and data_blocks.last.empty?
254+
data_blocks.pop
255+
end
256+
return data_blocks
257+
end
258+
259+
def send_write_request(&block)
260+
self.status = nil
261+
self.complete = false
262+
if block_given?
263+
start_server_socket {|msg| yield msg}
264+
else
265+
start_server_socket
266+
end
267+
self.client_sock = Rex::Socket::Udp.create(
268+
'PeerHost' => peer_host,
269+
'PeerPort' => peer_port,
270+
'LocalHost' => local_host,
271+
'LocalPort' => local_port,
272+
'Context' => context
273+
)
274+
self.client_sock.sendto(wrq_packet, peer_host, peer_port)
275+
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
276+
if block_given?
277+
monitor_client_sock {|msg| yield msg}
278+
else
279+
monitor_client_sock
280+
end
281+
}
282+
until self.complete
283+
return self.status
284+
end
285+
end
286+
287+
def send_data(host,port)
288+
self.status = {:write_allowed => true}
289+
data_blocks = blockify_file_or_data()
290+
if data_blocks.empty?
291+
yield "Closing down since there is no data to send." if block_given?
292+
self.status = {:success => [self.local_file, self.local_file, 0, 0]}
293+
return nil
294+
end
295+
sent_data = 0
296+
sent_blocks = 0
297+
expected_blocks = data_blocks.size
298+
expected_size = data_blocks.join.size
299+
if block_given?
300+
yield "Source file: #{self.local_file =~ /^DATA:/ ? "(Data)" : self.remote_file}, destination file: #{self.remote_file}"
301+
yield "Sending #{expected_size} bytes (#{expected_blocks} blocks)"
302+
end
303+
data_blocks.each_with_index do |data_block,idx|
304+
req = [OpData, (idx + 1), data_block].pack("nnA*")
305+
if self.server_sock.sendto(req, host, port) > 0
306+
sent_data += data_block.size
307+
end
308+
res = self.server_sock.recvfrom(65535)
309+
if res
310+
code, type, msg = parse_tftp_response(res[0])
311+
if code == 4
312+
sent_blocks += 1
313+
yield "Sent #{data_block.size} bytes in block #{sent_blocks}" if block_given?
314+
else
315+
if block_given?
316+
yield "Got an unexpected response: Code:%d, Type:%d, Message:'%s'. Aborting." % [code, type, msg]
317+
end
318+
break
319+
end
320+
end
321+
end
322+
if block_given?
323+
if(sent_data == expected_size)
324+
yield("Transferred #{sent_data} bytes in #{sent_blocks} blocks, upload complete!")
325+
else
326+
yield "Upload complete, but with errors."
327+
end
328+
end
329+
if sent_data == expected_size
330+
self.status = {:success => [
331+
self.local_file,
332+
self.remote_file,
333+
sent_data,
334+
sent_blocks
335+
] }
336+
end
337+
end
338+
339+
end
340+
341+
end
342+
end
343+
end

0 commit comments

Comments
 (0)