lib/rex/proto/tftp/client.rb in librex-0.0.68 vs lib/rex/proto/tftp/client.rb in librex-0.0.70
- old
+ new
@@ -30,313 +30,313 @@
# if you want to send "netascii" data, it's on you to fix up your source data
# prior to sending it.
#
class Client
- attr_accessor :local_host, :local_port, :peer_host, :peer_port
- attr_accessor :threads, :context, :server_sock, :client_sock
- attr_accessor :local_file, :remote_file, :mode, :action
- attr_accessor :complete, :recv_tempfile, :status
- attr_accessor :block_size # This definitely breaks spec, should only use for fuzz/sploit.
+ attr_accessor :local_host, :local_port, :peer_host, :peer_port
+ attr_accessor :threads, :context, :server_sock, :client_sock
+ attr_accessor :local_file, :remote_file, :mode, :action
+ attr_accessor :complete, :recv_tempfile, :status
+ attr_accessor :block_size # This definitely breaks spec, should only use for fuzz/sploit.
- # Returns an array of [code, type, msg]. Data packets
- # specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
- 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
+ # Returns an array of [code, type, msg]. Data packets
+ # specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
+ 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
- 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
+ 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
- #
- # Methods for both upload and download
- #
-
- 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
+ #
+ # Methods for both upload and download
+ #
- 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
+ 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
- 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
+ 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
- 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
+ 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
- #
- # Methods for download
- #
-
- 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
+ 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
- def ack_packet(blocknum=0)
- req = [OpAck, blocknum].pack("nn")
- end
+ #
+ # Methods for download
+ #
- 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
+ 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
- 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
+ def ack_packet(blocknum=0)
+ req = [OpAck, blocknum].pack("nn")
+ end
- 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
+ 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
- #
- # Methods for upload
- #
-
- 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
+ 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
- # 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.
- 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
+ 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
- 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 upload
+ #
- 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
+ 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
+
+ # 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.
+ 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
+
+ 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
+
+ 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
end
end
end