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