Parent

Class/Module Index [+]

Quicksearch

Rex::Proto::TFTP::Server

TFTP Server class

Attributes

context[RW]
files[RW]
incoming_file_hook[RW]
listen_host[RW]
listen_port[RW]
sock[RW]
thread[RW]
transfers[RW]
uploaded[RW]

Public Class Methods

new(port = 69, listen_host = '0.0.0.0', context = {}) click to toggle source
# File lib/rex/proto/tftp/server.rb, line 29
def initialize(port = 69, listen_host = '0.0.0.0', context = {})
        self.listen_host = listen_host
        self.listen_port = port
        self.context = context
        self.sock = nil
        @shutting_down = false
        @output_dir = nil
        @tftproot = nil

        self.files = []
        self.uploaded = []
        self.transfers = []
end

Public Instance Methods

find_file(fname) click to toggle source

Find the hash entry for a file that may be offered

# File lib/rex/proto/tftp/server.rb, line 134
def find_file(fname)
        # Files served via register_file() take precedence.
        self.files.each do |f|
                if (fname == f[:name])
                        return f
                end
        end

        # Now, if we have a tftproot, see if it can serve from it
        if @tftproot
                return find_file_in_root(fname)
        end

        nil
end
find_file_in_root(fname) click to toggle source

Find the file in the specified tftp root and add a temporary entry to the files hash.

# File lib/rex/proto/tftp/server.rb, line 155
def find_file_in_root(fname)
        fn = ::File.expand_path(::File.join(@tftproot, fname))

        # Don't allow directory traversal
        return nil if fn.index(@tftproot) != 0

        return nil if not ::File.file?(fn) or not ::File.readable?(fn)

        # Read the file contents, and register it as being served once
        data = data = ::File.open(fn, "rb") { |fd| fd.read(fd.stat.size) }
        register_file(fname, data)

        # Return the last file in the array
        return self.files[-1]
end
register_file(fn, content, once = false) click to toggle source

Register a filename and content for a client to request

# File lib/rex/proto/tftp/server.rb, line 83
def register_file(fn, content, once = false)
        self.files << {
                :name => fn,
                :data => content,
                :once => once
        }
end
send_error(from, num) click to toggle source

Send an error packet w/the specified code and string

# File lib/rex/proto/tftp/server.rb, line 111
def send_error(from, num)
        if (num < 1 or num >= ERRCODES.length)
                # ignore..
                return
        end
        pkt = [OpError, num].pack('nn')
        pkt << ERRCODES[num]
        pkt << "\x00"
        send_packet(from, pkt)
end
send_packet(from, pkt) click to toggle source

Send a single packet to the specified host

# File lib/rex/proto/tftp/server.rb, line 126
def send_packet(from, pkt)
        self.sock.sendto(pkt, from[0], from[1])
end
set_output_dir(outdir) click to toggle source

Register a directory to write uploaded files to

# File lib/rex/proto/tftp/server.rb, line 103
def set_output_dir(outdir)
        @output_dir = outdir if ::File.directory?(outdir)
end
set_tftproot(rootdir) click to toggle source

Register an entire directory to serve files from

# File lib/rex/proto/tftp/server.rb, line 95
def set_tftproot(rootdir)
        @tftproot = rootdir if ::File.directory?(rootdir)
end
start() click to toggle source

Start the TFTP server

# File lib/rex/proto/tftp/server.rb, line 47
def start
        self.sock = Rex::Socket::Udp.create(
                'LocalHost' => listen_host,
                'LocalPort' => listen_port,
                'Context'   => context
                )

        self.thread = Rex::ThreadFactory.spawn("TFTPServerMonitor", false) {
                monitor_socket
        }
end
stop() click to toggle source

Stop the TFTP server

# File lib/rex/proto/tftp/server.rb, line 63
def stop
        @shutting_down = true

        # Wait a maximum of 30 seconds for all transfers to finish.
        start = ::Time.now
        while (self.transfers.length > 0)
                ::IO.select(nil, nil, nil, 0.5)
                dur = ::Time.now - start
                break if (dur > 30)
        end

        self.files.clear
        self.thread.kill
        self.sock.close rescue nil # might be closed already
end

Protected Instance Methods

check_retransmission(tr) click to toggle source
# File lib/rex/proto/tftp/server.rb, line 206
def check_retransmission(tr)
        elapsed = ::Time.now - tr[:last_sent]
        if (elapsed >= tr[:timeout])
                # max retries reached?
                if (tr[:retries] < 3)
                        #if (tr[:type] == OpRead)
                        #   puts "[-] ack timed out, resending block"
                        #else
                        #   puts "[-] block timed out, resending ack"
                        #end
                        tr[:last_sent] = nil
                        tr[:retries] += 1
                else
                        #puts "[-] maximum tries reached, terminating transfer"
                        self.transfers.delete(tr)
                end
        end
end
dispatch_request(from, buf) click to toggle source

Dispatch a packet that we received

# File lib/rex/proto/tftp/server.rb, line 313
def dispatch_request(from, buf)

        op = buf.unpack('n')[0]
        buf.slice!(0,2)

        #XXX: todo - create call backs for status
        #start = "[*] TFTP - %s:%u - %s" % [from[0], from[1], OPCODES[op]]

        case op
        when OpRead
                # Process RRQ packets
                fn = TFTP::get_string(buf)
                mode = TFTP::get_string(buf).downcase

                #puts "%s %s %s" % [start, fn, mode]

                if (not @shutting_down) and (file = self.find_file(fn))
                        if (file[:once] and file[:started])
                                send_error(from, ErrFileNotFound)
                        else
                                transfer = {
                                        :type => OpRead,
                                        :from => from,
                                        :file => file,
                                        :block => 1,
                                        :blksize => 512,
                                        :offset => 0,
                                        :timeout => 3,
                                        :last_sent => nil,
                                        :retries => 0
                                }

                                process_options(from, buf, transfer)

                                self.transfers << transfer
                        end
                else
                        #puts "[-] file not found!"
                        send_error(from, ErrFileNotFound)
                end

        when OpWrite
                # Process WRQ packets
                fn = TFTP::get_string(buf)
                mode = TFTP::get_string(buf).downcase

                #puts "%s %s %s" % [start, fn, mode]

                if not @shutting_down
                        transfer = {
                                :type => OpWrite,
                                :from => from,
                                :file => { :name => fn, :data => '' },
                                :block => 0, # WRQ starts at 0
                                :blksize => 512,
                                :timeout => 3,
                                :last_sent => nil,
                                :retries => 0
                        }

                        process_options(from, buf, transfer)

                        self.transfers << transfer
                else
                        send_error(from, ErrIllegalOperation)
                end

        when OpAck
                # Process ACK packets
                block = buf.unpack('n')[0]

                #puts "%s %d" % [start, block]

                tr = find_transfer(OpRead, from, block)
                if not tr
                        # NOTE: some clients, such as pxelinux, send an ack for block 0.
                        # To deal with this, we simply ignore it as we start with block 1.
                        return if block == 0

                        # If we didn't find it, send an error.
                        send_error(from, ErrUnknownTransferId)
                else
                        # acked! send the next block
                        tr[:offset] += tr[:blksize]
                        next_block(tr)

                        # If the transfer is finished, delete it
                        if (tr[:offset] > tr[:file][:data].length)
                                #puts "[*] Transfer complete"
                                self.transfers.delete(tr)

                                # if the file is a one-serve, delete it from the files array
                                if tr[:file][:once]
                                        #puts "[*] Removed one-serve file: #{tr[:file][:name]}"
                                        self.files.delete(tr[:file])
                                end
                        end
                end

        when OpData
                # Process Data packets
                block = buf.unpack('n')[0]
                data = buf.slice(2, buf.length)

                #puts "%s %d %d bytes" % [start, block, data.length]

                tr = find_transfer(OpWrite, from, (block-1))
                if not tr
                        # If we didn't find it, send an error.
                        send_error(from, ErrUnknownTransferId)
                else
                        tr[:file][:data] << data
                        tr[:last_size] = data.length
                        next_block(tr)

                        # Similar to RRQ transfers, we cannot detect that the
                        # transfer finished here. We must do so after transmitting
                        # the final ACK.
                end

        else
                # Other packets are unsupported
                #puts start
                send_error(from, ErrAccessViolation)

        end
end
find_transfer(type, from, block) click to toggle source
# File lib/rex/proto/tftp/server.rb, line 180
def find_transfer(type, from, block)
        self.transfers.each do |tr|
                if (tr[:type] == type and tr[:from] == from and tr[:block] == block)
                        return tr
                end
        end
        nil
end
monitor_socket() click to toggle source

See if there is anything to do.. If so, dispatch it.

# File lib/rex/proto/tftp/server.rb, line 229
def monitor_socket
        while true
                rds = [@sock]
                wds = []
                self.transfers.each do |tr|
                        if (not tr[:last_sent])
                                wds << @sock
                                break
                        end
                end
                eds = [@sock]

                r,w,e = ::IO.select(rds,wds,eds,1)

                if (r != nil and r[0] == self.sock)
                        buf,host,port = self.sock.recvfrom(65535)
                        # Lame compatabilitiy :-/
                        from = [host, port]
                        dispatch_request(from, buf)
                end

                #
                # Check to see if transfers need maintenance
                #
                self.transfers.each do |tr|
                        # We handle RRQ and WRQ separately
                        #
                        if (tr[:type] == OpRead)
                                # Are we awaiting an ack?
                                if (tr[:last_sent])
                                        check_retransmission(tr)
                                elsif (w != nil and w[0] == self.sock)
                                        # No ack waiting, send next block..
                                        chunk = tr[:file][:data].slice(tr[:offset], tr[:blksize])
                                        if (chunk and chunk.length >= 0)
                                                pkt = [OpData, tr[:block]].pack('nn')
                                                pkt << chunk

                                                send_packet(tr[:from], pkt)
                                                tr[:last_sent] = ::Time.now

                                                # If the file is a one-serve, mark it as started
                                                tr[:file][:started] = true if (tr[:file][:once])
                                        else
                                                # No more chunks.. transfer is most likely done.
                                                # However, we can only delete it once the last chunk has been
                                                # acked.
                                        end
                                end
                        else
                                # Are we awaiting data?
                                if (tr[:last_sent])
                                        check_retransmission(tr)
                                elsif (w != nil and w[0] == self.sock)
                                        # Not waiting for data, send an ack..
                                        #puts "[*] sending ack for block %d" % [tr[:block]]
                                        pkt = [OpAck, tr[:block]].pack('nn')

                                        send_packet(tr[:from], pkt)
                                        tr[:last_sent] = ::Time.now

                                        # If we had a 0-511 byte chunk, we're done.
                                        if (tr[:last_size] and tr[:last_size] < tr[:blksize])
                                                #puts "[*] Transfer complete, saving output"
                                                save_output(tr)
                                                self.transfers.delete(tr)
                                        end
                                end
                        end
                end
        end
end
next_block(tr) click to toggle source
# File lib/rex/proto/tftp/server.rb, line 303
def next_block(tr)
        tr[:block] += 1
        tr[:last_sent] = nil
        tr[:retries] = 0
end
process_options(from, buf, tr) click to toggle source
# File lib/rex/proto/tftp/server.rb, line 441
def process_options(from, buf, tr)
        found = 0
        to_ack = []
        while buf.length >= 4
                opt = TFTP::get_string(buf)
                break if not opt
                val = TFTP::get_string(buf)
                break if not val

                found += 1

                # Is it one we support?
                opt.downcase!

                case opt
                when "blksize"
                        val = val.to_i
                        if val > 0
                                tr[:blksize] = val
                                to_ack << [ opt, val.to_s ]
                        end

                when "timeout"
                        val = val.to_i
                        if val >= 1 and val <= 255
                                tr[:timeout] = val
                                to_ack << [ opt, val.to_s ]
                        end

                when "tsize"
                        if tr[:type] == OpRead
                                len = tr[:file][:data].length
                        else
                                val = val.to_i
                                len = val
                        end
                        to_ack << [ opt, len.to_s ]

                end
        end

        return if to_ack.length < 1

        # if we have anything to ack, do it
        data = [OpOptAck].pack('n')
        to_ack.each { |el|
                data << el[0] << "\x00" << el[1] << "\x00"
        }

        send_packet(from, data)
end
save_output(tr) click to toggle source
# File lib/rex/proto/tftp/server.rb, line 189
def save_output(tr)
        self.uploaded << tr[:file]
        
        return incoming_file_hook.call(tr) if incoming_file_hook
        
        if @output_dir
                fn = tr[:file][:name].split(File::SEPARATOR)[-1]
                if fn
                        fn = ::File.join(@output_dir, Rex::FileUtils.clean_path(fn))
                        ::File.open(fn, "wb") { |fd|
                                fd.write(tr[:file][:data])
                        }
                end
        end
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.