Parent

Class/Module Index [+]

Quicksearch

Rex::Proto::TFTP::Client

TFTP Client class

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

action[RW]
block_size[RW]
client_sock[RW]
complete[RW]
context[RW]
local_file[RW]
local_host[RW]
local_port[RW]
mode[RW]
peer_host[RW]
peer_port[RW]
recv_tempfile[RW]
remote_file[RW]
server_sock[RW]
status[RW]
threads[RW]

Public Class Methods

new(params) click to toggle source
# 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

ack_packet(blocknum=0) click to toggle source
# File lib/rex/proto/tftp/client.rb, line 147
def ack_packet(blocknum=0)
        req = [OpAck, blocknum].pack("nn")
end
blockify_file_or_data() click to toggle source

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:(.*)/
                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}}/)
        # Drop any trailing empty blocks
        if data_blocks.size > 1 and data_blocks.last.empty?
                data_blocks.pop
        end
        return data_blocks
end
monitor_client_sock() click to toggle source
# 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
monitor_server_sock() click to toggle source
# 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
parse_tftp_response(str) click to toggle source

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
recv_data(host, port, first_block) click to toggle source
# 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
rrq_packet() click to toggle source

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
send_data(host,port) click to toggle source
# 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
send_read_request(&block) click to toggle source
# 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
send_write_request(&block) click to toggle source
# 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
start_server_socket() click to toggle source

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
stop() click to toggle source
# 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
write_and_ack_data(data,blocknum,host,port) click to toggle source
# 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
wrq_packet() click to toggle source

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

[Validate]

Generated with the Darkfish Rdoc Generator 2.