class Rex::Proto::TFTP::Client
Note that TFTP
has blocks, and so does Ruby. Watch out with the variable names!
The big gotcha right now is that setting the mode between octet, netascii, or anything else doesn’t actually do anything other than declare it to the server.
Also, since TFTP
clients act as both clients and servers, we use two threads to handle transfers, regardless of the direction. For this reason, the transfer actions are nonblocking; if you need to see the results of a transfer before doing something else, check the boolean complete attribute and any return data in the :status attribute. It’s a little weird like that.
Finally, most (all?) clients will alter the data in netascii mode in order to try to conform to the RFC standard for what “netascii” means, but there are ambiguities in implementations on things like if nulls are allowed, what to do with Unicode, and all that. For this reason, “octet” is default, and if you want to send “netascii” data, it’s on you to fix up your source data prior to sending it.
Attributes
Public Class Methods
# File lib/rex/proto/tftp/client.rb, line 49 def initialize(params) self.threads = [] self.local_host = params["LocalHost"] || "0.0.0.0" self.local_port = params["LocalPort"] || (1025 + rand(0xffff-1025)) self.peer_host = params["PeerHost"] || (raise ArgumentError, "Need a peer host.") self.peer_port = params["PeerPort"] || 69 self.context = params["Context"] self.local_file = params["LocalFile"] self.remote_file = params["RemoteFile"] || (::File.split(self.local_file).last if self.local_file) self.mode = params["Mode"] || "octet" self.action = params["Action"] || (raise ArgumentError, "Need an action.") self.block_size = params["BlockSize"] || 512 end
Public Instance Methods
# File lib/rex/proto/tftp/client.rb, line 147 def ack_packet(blocknum=0) req = [OpAck, blocknum].pack("nn") end
Note that the local filename for uploading need not be a real filename – if it begins with DATA: it can be any old string of bytes. If it’s missing completely, then just quit.
# File lib/rex/proto/tftp/client.rb, line 243 def blockify_file_or_data if self.local_file =~ /^DATA:(.*)/m data = $1 elsif ::File.file?(self.local_file) and ::File.readable?(self.local_file) data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size} rescue [] else return [] end data_blocks = data.scan(/.{1,#{block_size}}/m) # Drop any trailing empty blocks if data_blocks.size > 1 and data_blocks.last.empty? data_blocks.pop end return data_blocks end
# File lib/rex/proto/tftp/client.rb, line 114 def monitor_client_sock res = self.client_sock.recvfrom(65535) if res[1] # Got a response back, so that's never good; Acks come back on server_sock. code, type, data = parse_tftp_response(res[0]) yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given? self.status = {:error => [code, type, data]} stop end end
# File lib/rex/proto/tftp/client.rb, line 85 def monitor_server_sock yield "Listening for incoming ACKs" if block_given? res = self.server_sock.recvfrom(65535) if res and res[0] code, type, data = parse_tftp_response(res[0]) if code == OpAck and self.action == :upload if block_given? yield "WRQ accepted, sending the file." if type == 0 send_data(res[1], res[2]) {|msg| yield msg} else send_data(res[1], res[2]) end elsif code == OpData and self.action == :download if block_given? recv_data(res[1], res[2], data) {|msg| yield msg} else recv_data(res[1], res[2], data) end elsif code == OpError yield("Aborting, got error type:%d, message:'%s'" % [type, data]) if block_given? self.status = {:error => [code, type, data]} else yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, data]) if block_given? self.status = {:error => [code, type, data]} end end stop end
Returns an array of [code, type, msg]. Data packets specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
# File lib/rex/proto/tftp/client.rb, line 42 def parse_tftp_response(str) return nil unless str.length >= 4 ret = str.unpack("nnA*") ret[2] = str[4,str.size] if ret[0] == OpData return ret end
# File lib/rex/proto/tftp/client.rb, line 179 def recv_data(host, port, first_block) self.recv_tempfile = Rex::Quickfile.new('msf-tftp') recvd_blocks = 1 if block_given? yield "Source file: #{self.remote_file}, destination file: #{self.local_file}" yield "Received and acknowledged #{first_block.size} in block #{recvd_blocks}" end if block_given? write_and_ack_data(first_block,1,host,port) {|msg| yield msg} else write_and_ack_data(first_block,1,host,port) end current_block = first_block while current_block.size == 512 res = self.server_sock.recvfrom(65535) if res and res[0] code, block_num, current_block = parse_tftp_response(res[0]) if code == 3 if block_given? write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg} else write_and_ack_data(current_block,block_num,host,port) end recvd_blocks += 1 else yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given? stop end end end if block_given? yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!") end self.status = {:success => [ self.local_file, self.remote_file, self.recv_tempfile.size, recvd_blocks.size] } self.recv_tempfile.close stop end
Methods for download
# File lib/rex/proto/tftp/client.rb, line 141 def rrq_packet req = [OpRead, self.remote_file, self.mode] packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}" req.pack(packstr) end
# File lib/rex/proto/tftp/client.rb, line 287 def send_data(host,port) self.status = {:write_allowed => true} data_blocks = blockify_file_or_data() if data_blocks.empty? yield "Closing down since there is no data to send." if block_given? self.status = {:success => [self.local_file, self.local_file, 0, 0]} return nil end sent_data = 0 sent_blocks = 0 expected_blocks = data_blocks.size expected_size = data_blocks.join.size if block_given? yield "Source file: #{self.local_file =~ /^DATA:/ ? "(Data)" : self.remote_file}, destination file: #{self.remote_file}" yield "Sending #{expected_size} bytes (#{expected_blocks} blocks)" end data_blocks.each_with_index do |data_block,idx| req = [OpData, (idx + 1), data_block].pack("nnA*") if self.server_sock.sendto(req, host, port) > 0 sent_data += data_block.size end res = self.server_sock.recvfrom(65535) if res code, type, msg = parse_tftp_response(res[0]) if code == 4 sent_blocks += 1 yield "Sent #{data_block.size} bytes in block #{sent_blocks}" if block_given? else if block_given? yield "Got an unexpected response: Code:%d, Type:%d, Message:'%s'. Aborting." % [code, type, msg] end break end end end if block_given? if(sent_data == expected_size) yield("Transferred #{sent_data} bytes in #{sent_blocks} blocks, upload complete!") else yield "Upload complete, but with errors." end end if sent_data == expected_size self.status = {:success => [ self.local_file, self.remote_file, sent_data, sent_blocks ] } end end
# File lib/rex/proto/tftp/client.rb, line 151 def send_read_request(&block) self.status = nil self.complete = false if block_given? start_server_socket {|msg| yield msg} else start_server_socket end self.client_sock = Rex::Socket::Udp.create( 'PeerHost' => peer_host, 'PeerPort' => peer_port, 'LocalHost' => local_host, 'LocalPort' => local_port, 'Context' => context ) self.client_sock.sendto(rrq_packet, peer_host, peer_port) self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) { if block_given? monitor_client_sock {|msg| yield msg} else monitor_client_sock end } until self.complete return self.status end end
# File lib/rex/proto/tftp/client.rb, line 259 def send_write_request(&block) self.status = nil self.complete = false if block_given? start_server_socket {|msg| yield msg} else start_server_socket end self.client_sock = Rex::Socket::Udp.create( 'PeerHost' => peer_host, 'PeerPort' => peer_port, 'LocalHost' => local_host, 'LocalPort' => local_port, 'Context' => context ) self.client_sock.sendto(wrq_packet, peer_host, peer_port) self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) { if block_given? monitor_client_sock {|msg| yield msg} else monitor_client_sock end } until self.complete return self.status end end
Methods for both upload and download
# File lib/rex/proto/tftp/client.rb, line 67 def start_server_socket self.server_sock = Rex::Socket::Udp.create( 'LocalHost' => local_host, 'LocalPort' => local_port, 'Context' => context ) if self.server_sock and block_given? yield "Started TFTP client listener on #{local_host}:#{local_port}" end self.threads << Rex::ThreadFactory.spawn("TFTPServerMonitor", false) { if block_given? monitor_server_sock {|msg| yield msg} else monitor_server_sock end } end
# File lib/rex/proto/tftp/client.rb, line 124 def stop self.complete = true begin self.server_sock.close self.client_sock.close self.server_sock = nil self.client_sock = nil self.threads.each {|t| t.kill} rescue nil end end
# File lib/rex/proto/tftp/client.rb, line 222 def write_and_ack_data(data,blocknum,host,port) self.recv_tempfile.write(data) self.recv_tempfile.flush req = ack_packet(blocknum) self.server_sock.sendto(req, host, port) yield "Received and acknowledged #{data.size} in block #{blocknum}" if block_given? end
Methods for upload
# File lib/rex/proto/tftp/client.rb, line 234 def wrq_packet req = [OpWrite, self.remote_file, self.mode] packstr = "na#{self.remote_file.length+1}a#{self.mode.length+1}" req.pack(packstr) end