TFTP adding comment docs, ability to send w/out a file.
Commenting the tricksy parts a little better for general usage. Adding the ability to set FILEDATA instead of FILENAME, in case only short bits of data are desired and the user doesn't want to go to the trouble of creating a source file to upload.unstable
parent
431ef826c9
commit
2b3e3725ac
|
@ -12,12 +12,31 @@ module TFTP
|
||||||
# TFTP Client class
|
# TFTP Client class
|
||||||
#
|
#
|
||||||
# Note that TFTP has blocks, and so does Ruby. Watch out with the variable names!
|
# 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 action of sending data is 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 :return_data 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.
|
||||||
|
#
|
||||||
class Client
|
class Client
|
||||||
|
|
||||||
attr_accessor :local_host, :local_port, :peer_host, :peer_port
|
attr_accessor :local_host, :local_port, :peer_host, :peer_port
|
||||||
attr_accessor :threads, :context, :server_sock, :client_sock
|
attr_accessor :threads, :context, :server_sock, :client_sock
|
||||||
attr_accessor :local_file, :remote_file, :mode, :action
|
attr_accessor :local_file, :remote_file, :mode, :action
|
||||||
attr_accessor :complete, :recv_tempfile
|
attr_accessor :complete, :recv_tempfile, :return_data
|
||||||
|
|
||||||
# Returns an array of [code, type, msg]. Data packets
|
# Returns an array of [code, type, msg]. Data packets
|
||||||
# specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
|
# specifically will /not/ unpack, since that would drop any trailing spaces or nulls.
|
||||||
|
@ -35,7 +54,7 @@ class Client
|
||||||
self.peer_host = params["PeerHost"] || (raise ArgumentError, "Need a peer host.")
|
self.peer_host = params["PeerHost"] || (raise ArgumentError, "Need a peer host.")
|
||||||
self.peer_port = params["PeerPort"] || 69
|
self.peer_port = params["PeerPort"] || 69
|
||||||
self.context = params["Context"] || {}
|
self.context = params["Context"] || {}
|
||||||
self.local_file = params["LocalFile"] || (raise ArgumentError, "Need a local file.")
|
self.local_file = params["LocalFile"]
|
||||||
self.remote_file = params["RemoteFile"] || ::File.split(self.local_file).last
|
self.remote_file = params["RemoteFile"] || ::File.split(self.local_file).last
|
||||||
self.mode = params["Mode"] || "octet"
|
self.mode = params["Mode"] || "octet"
|
||||||
self.action = params["Action"] || (raise ArgumentError, "Need an action.")
|
self.action = params["Action"] || (raise ArgumentError, "Need an action.")
|
||||||
|
@ -55,7 +74,11 @@ class Client
|
||||||
yield "Started TFTP client listener on #{local_host}:#{local_port}"
|
yield "Started TFTP client listener on #{local_host}:#{local_port}"
|
||||||
end
|
end
|
||||||
self.threads << Rex::ThreadFactory.spawn("TFTPServerMonitor", false) {
|
self.threads << Rex::ThreadFactory.spawn("TFTPServerMonitor", false) {
|
||||||
monitor_server_sock {|msg| yield msg}
|
if block_given?
|
||||||
|
monitor_server_sock {|msg| yield msg}
|
||||||
|
else
|
||||||
|
monitor_server_sock
|
||||||
|
end
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -65,9 +88,19 @@ class Client
|
||||||
if res and res[0]
|
if res and res[0]
|
||||||
code, type, data = parse_tftp_msg(res[0])
|
code, type, data = parse_tftp_msg(res[0])
|
||||||
if code == OpAck && self.action == :upload
|
if code == OpAck && self.action == :upload
|
||||||
send_data(res[1], res[2]) {|msg| yield msg}
|
if block_given?
|
||||||
|
yield "WRQ accepted, sending the file." if type == 0
|
||||||
|
self.return_data = {:write_allowed => true}
|
||||||
|
send_data(res[1], res[2]) {|msg| yield msg}
|
||||||
|
else
|
||||||
|
send_data(res[1], res[2])
|
||||||
|
end
|
||||||
elsif code == OpData && self.action == :download
|
elsif code == OpData && self.action == :download
|
||||||
recv_data(res[1], res[2], data) {|msg| yield msg}
|
if block_given?
|
||||||
|
recv_data(res[1], res[2], data) {|msg| yield msg}
|
||||||
|
else
|
||||||
|
recv_data(res[1], res[2], data)
|
||||||
|
end
|
||||||
else
|
else
|
||||||
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
||||||
stop
|
stop
|
||||||
|
@ -107,6 +140,8 @@ class Client
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_read_request(&block)
|
def send_read_request(&block)
|
||||||
|
self.return_data = nil
|
||||||
|
self.complete = false
|
||||||
if block_given?
|
if block_given?
|
||||||
start_server_socket {|msg| yield msg}
|
start_server_socket {|msg| yield msg}
|
||||||
else
|
else
|
||||||
|
@ -121,8 +156,15 @@ class Client
|
||||||
)
|
)
|
||||||
self.client_sock.sendto(rrq_packet, peer_host, peer_port)
|
self.client_sock.sendto(rrq_packet, peer_host, peer_port)
|
||||||
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
|
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
|
||||||
monitor_client_sock {|msg| yield msg}
|
if block_given?
|
||||||
|
monitor_client_sock {|msg| yield msg}
|
||||||
|
else
|
||||||
|
monitor_client_sock
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
until self.complete
|
||||||
|
return self.return_data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def recv_data(host, port, first_block)
|
def recv_data(host, port, first_block)
|
||||||
|
@ -132,14 +174,22 @@ class Client
|
||||||
yield "Source file: #{self.remote_file}, destination file: #{self.local_file}"
|
yield "Source file: #{self.remote_file}, destination file: #{self.local_file}"
|
||||||
yield "Received and acknowledged #{first_block.size} in block #{recvd_blocks}"
|
yield "Received and acknowledged #{first_block.size} in block #{recvd_blocks}"
|
||||||
end
|
end
|
||||||
write_and_ack_data(first_block,1,host,port) {|msg| yield msg}
|
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
|
current_block = first_block
|
||||||
while current_block.size == 512
|
while current_block.size == 512
|
||||||
res = self.server_sock.recvfrom(65535)
|
res = self.server_sock.recvfrom(65535)
|
||||||
if res and res[0]
|
if res and res[0]
|
||||||
code, block_num, current_block = parse_tftp_msg(res[0])
|
code, block_num, current_block = parse_tftp_msg(res[0])
|
||||||
if code == 3
|
if code == 3
|
||||||
write_and_ack_data(current_block,block_num,host,port) {|msg| yield msg}
|
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
|
recvd_blocks += 1
|
||||||
else
|
else
|
||||||
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
yield("Aborting, got code:%d, type:%d, message:'%s'" % [code, type, msg]) if block_given?
|
||||||
|
@ -147,7 +197,15 @@ class Client
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
yield("Transferred #{recvd_blocks} blocks, #{self.recv_tempfile.size} bytes, download complete!")
|
if block_given?
|
||||||
|
yield("Transferred #{self.recv_tempfile.size} bytes in #{recvd_blocks} blocks, download complete!")
|
||||||
|
end
|
||||||
|
self.return_data = {:success => [
|
||||||
|
self.local_file,
|
||||||
|
self.remote_file,
|
||||||
|
self.recv_tempfile.size,
|
||||||
|
recvd_blocks.size]
|
||||||
|
}
|
||||||
self.recv_tempfile.close
|
self.recv_tempfile.close
|
||||||
stop
|
stop
|
||||||
end
|
end
|
||||||
|
@ -157,7 +215,7 @@ class Client
|
||||||
self.recv_tempfile.flush
|
self.recv_tempfile.flush
|
||||||
req = ack_packet(blocknum)
|
req = ack_packet(blocknum)
|
||||||
self.server_sock.sendto(req, host, port)
|
self.server_sock.sendto(req, host, port)
|
||||||
yield "Received and acknowledged #{data.size} in block #{blocknum}"
|
yield "Received and acknowledged #{data.size} in block #{blocknum}" if block_given?
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -170,12 +228,20 @@ class Client
|
||||||
req.pack(packstr)
|
req.pack(packstr)
|
||||||
end
|
end
|
||||||
|
|
||||||
def blockify_file
|
# Note that the local filename for uploading need not be a real filename --
|
||||||
data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size}
|
# if it begins with DATA: it can be any old string of bytes.
|
||||||
|
def blockify_file_or_data
|
||||||
|
if self.local_file =~ /^DATA:(.*)/m
|
||||||
|
data = $1
|
||||||
|
else
|
||||||
|
data = ::File.open(self.local_file, "rb") {|f| f.read f.stat.size}
|
||||||
|
end
|
||||||
data.scan(/.{1,512}/)
|
data.scan(/.{1,512}/)
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_write_request(&block)
|
def send_write_request(&block)
|
||||||
|
self.return_data = nil
|
||||||
|
self.complete = false
|
||||||
if block_given?
|
if block_given?
|
||||||
start_server_socket {|msg| yield msg}
|
start_server_socket {|msg| yield msg}
|
||||||
else
|
else
|
||||||
|
@ -190,18 +256,25 @@ class Client
|
||||||
)
|
)
|
||||||
self.client_sock.sendto(wrq_packet, peer_host, peer_port)
|
self.client_sock.sendto(wrq_packet, peer_host, peer_port)
|
||||||
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
|
self.threads << Rex::ThreadFactory.spawn("TFTPClientMonitor", false) {
|
||||||
monitor_client_sock {|msg| yield msg}
|
if block_given?
|
||||||
|
monitor_client_sock {|msg| yield msg}
|
||||||
|
else
|
||||||
|
monitor_client_sock
|
||||||
|
end
|
||||||
}
|
}
|
||||||
|
until self.complete
|
||||||
|
return self.return_data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def send_data(host,port)
|
def send_data(host,port)
|
||||||
data_blocks = blockify_file()
|
data_blocks = blockify_file_or_data()
|
||||||
sent_data = 0
|
sent_data = 0
|
||||||
sent_blocks = 0
|
sent_blocks = 0
|
||||||
expected_blocks = data_blocks.size
|
expected_blocks = data_blocks.size
|
||||||
expected_size = data_blocks.join.size
|
expected_size = data_blocks.join.size
|
||||||
if block_given?
|
if block_given?
|
||||||
yield "Source file: #{self.local_file}, destination file: #{self.remote_file}"
|
yield "Source file: #{self.local_file =~ /^DATA:/ ? "(Data)" : self.remote_file}, destination file: #{self.remote_file}"
|
||||||
yield "Sending #{expected_size} bytes (#{expected_blocks} blocks)"
|
yield "Sending #{expected_size} bytes (#{expected_blocks} blocks)"
|
||||||
end
|
end
|
||||||
data_blocks.each_with_index do |data_block,idx|
|
data_blocks.each_with_index do |data_block,idx|
|
||||||
|
@ -225,11 +298,19 @@ class Client
|
||||||
end
|
end
|
||||||
if block_given?
|
if block_given?
|
||||||
if(sent_data == expected_size)
|
if(sent_data == expected_size)
|
||||||
yield "Upload complete!"
|
yield("Transferred #{sent_data} bytes in #{sent_blocks} blocks, upload complete!")
|
||||||
else
|
else
|
||||||
yield "Upload complete, but with errors."
|
yield "Upload complete, but with errors."
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
if sent_data == expected_size
|
||||||
|
self.return_data = {:success => [
|
||||||
|
self.local_file,
|
||||||
|
self.remote_file,
|
||||||
|
sent_data,
|
||||||
|
sent_blocks
|
||||||
|
] }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,9 +17,17 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
super(
|
super(
|
||||||
'Name' => 'TFTP File Transfer Utility',
|
'Name' => 'TFTP File Transfer Utility',
|
||||||
'Description' => %q{
|
'Description' => %q{
|
||||||
This module will send file to a remote TFTP server. Note that the target
|
This module will transfer a file to or from a remote TFTP server.
|
||||||
must be able to connect back to the Metasploit system, and NAT traversal
|
Note that the target must be able to connect back to the Metasploit system,
|
||||||
for TFTP is often unsupported.
|
and NAT traversal for TFTP is often unsupported.
|
||||||
|
|
||||||
|
Two actions are supported: "Upload" and "Download," which behave as one might
|
||||||
|
expect -- use 'set action Actionname' to use either mode of operation.
|
||||||
|
|
||||||
|
If "Download" is selected, at least one of FILENAME or REMOTE_FILENAME
|
||||||
|
must be set. If "Upload" is selected, either FILENAME must be set to a valid path to
|
||||||
|
a source file, or FILEDATA must be populated. FILENAME may be a fully qualified path,
|
||||||
|
or the name of a file in the Msf::Config.local_directory or Msf::Config.data_directory.
|
||||||
},
|
},
|
||||||
'Author' => [ 'todb' ],
|
'Author' => [ 'todb' ],
|
||||||
'References' =>
|
'References' =>
|
||||||
|
@ -28,32 +36,25 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
['URL', 'http://www.networksorcery.com/enp/protocol/tftp.htm']
|
['URL', 'http://www.networksorcery.com/enp/protocol/tftp.htm']
|
||||||
],
|
],
|
||||||
'Actions' => [
|
'Actions' => [
|
||||||
[ 'Download', {'Description' => "Download REMOTE_FILENAME as FILENAME."}],
|
[ 'Download', {'Description' => "Download REMOTE_FILENAME as FILENAME from the server."}],
|
||||||
[ 'Upload', {'Description' => "Upload FILENAME as REMOTE_FILENAME to the server."}]
|
[ 'Upload', {'Description' => "Upload FILENAME as REMOTE_FILENAME to the server."}]
|
||||||
],
|
],
|
||||||
'DefaultAction' => 'Upload',
|
'DefaultAction' => 'Upload',
|
||||||
'License' => MSF_LICENSE
|
'License' => MSF_LICENSE
|
||||||
)
|
)
|
||||||
register_options([
|
register_options([
|
||||||
OptPath.new( 'FILENAME', [false, "The local filename" ]),
|
OptString.new( 'FILENAME', [false, "The local filename" ]),
|
||||||
|
OptString.new( 'FILEDATA', [false, "Data to upload in lieu of a real local file." ]),
|
||||||
OptString.new( 'REMOTE_FILENAME', [false, "The remote filename"]),
|
OptString.new( 'REMOTE_FILENAME', [false, "The remote filename"]),
|
||||||
OptAddress.new('RHOST', [true, "The remote TFTP server"]),
|
OptAddress.new('RHOST', [true, "The remote TFTP server"]),
|
||||||
OptPort.new( 'LPORT', [false, "The local port the TFTP client should listen on (default is random)" ]),
|
OptPort.new( 'LPORT', [false, "The local port the TFTP client should listen on (default is random)" ]),
|
||||||
OptAddress.new('LHOST', [false, "The local address the TFTP client should bind to"]),
|
OptAddress.new('LHOST', [false, "The local address the TFTP client should bind to"]),
|
||||||
OptBool.new( 'VERBOSE', [false, "Display verbose details about the transfer", false]),
|
OptBool.new( 'VERBOSE', [false, "Display verbose details about the transfer", false]),
|
||||||
OptString.new( 'MODE', [false, "The TFTP mode; usual choices are netascii and octet.", "octet"]),
|
OptString.new( 'MODE', [false, "The TFTP mode; usual choices are netascii and octet.", "octet"]),
|
||||||
Opt::RPORT(69)
|
Opt::RPORT(69)
|
||||||
], self.class)
|
], self.class)
|
||||||
end
|
end
|
||||||
|
|
||||||
def file
|
|
||||||
if action.name == "Upload"
|
|
||||||
datastore['FILENAME']
|
|
||||||
else # "Download
|
|
||||||
fname = ::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def mode
|
def mode
|
||||||
datastore['MODE'] || "octect"
|
datastore['MODE'] || "octect"
|
||||||
end
|
end
|
||||||
|
@ -70,6 +71,8 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
datastore['RHOST']
|
datastore['RHOST']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Used only to store loot, doesn't actually have any semantic meaning
|
||||||
|
# for the TFTP protocol.
|
||||||
def datatype
|
def datatype
|
||||||
case datastore['MODE']
|
case datastore['MODE']
|
||||||
when "netascii"
|
when "netascii"
|
||||||
|
@ -79,6 +82,34 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
# The local filename must be real if you are uploading. Otherwise,
|
||||||
|
# it can be made up, since for downloading it's only used for the
|
||||||
|
# name of the loot entry.
|
||||||
|
def file
|
||||||
|
if action.name == "Upload"
|
||||||
|
fname = datastore['FILENAME'].to_s
|
||||||
|
if fname.empty?
|
||||||
|
fdata = "DATA:#{datastore['FILEDATA']}"
|
||||||
|
return fdata
|
||||||
|
else
|
||||||
|
if ::File.readable? fname
|
||||||
|
fname
|
||||||
|
else
|
||||||
|
fname_local = ::File.join(Msf::Config.local_directory,fname)
|
||||||
|
fname_data = ::File.join(Msf::Config.data_directory,fname)
|
||||||
|
return fname_local if ::File.readable? fname_local
|
||||||
|
return fname_data if ::File.readable? fname_data
|
||||||
|
return nil # Couldn't find it, giving up.
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else # "Download
|
||||||
|
fname = ::File.split(datastore['FILENAME'] || datastore['REMOTE_FILENAME']).last rescue nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Experimental message prepending thinger. Might make it up into the
|
||||||
|
# standard Metasploit lib like vprint_status and friends.
|
||||||
def rtarget(ip=nil)
|
def rtarget(ip=nil)
|
||||||
if (ip or rhost) and rport
|
if (ip or rhost) and rport
|
||||||
[(ip || rhost),rport].map {|x| x.to_s}.join(":") << " "
|
[(ip || rhost),rport].map {|x| x.to_s}.join(":") << " "
|
||||||
|
@ -89,12 +120,16 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# At least one, but not both, are required.
|
||||||
def check_valid_filename
|
def check_valid_filename
|
||||||
not (datastore['FILENAME'].to_s.empty? and datastore['REMOTE_FILENAME'].to_s.empty?)
|
not (datastore['FILENAME'].to_s.empty? and datastore['REMOTE_FILENAME'].to_s.empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
#
|
# This all happens before run(), and should give an idea on how to use
|
||||||
# TFTP is a funny service and needs to kind of be a server on our side, too.
|
# the TFTP client mixin. Essentially, you create an instance of the
|
||||||
|
# Rex::Proto::TFTP::Client class, fill it up with the relevant host and
|
||||||
|
# file data, set it to either :upload or :download, then kick off the
|
||||||
|
# transfer as you like.
|
||||||
def setup
|
def setup
|
||||||
unless check_valid_filename()
|
unless check_valid_filename()
|
||||||
print_error "Need at least one valid filename."
|
print_error "Need at least one valid filename."
|
||||||
|
@ -106,14 +141,14 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
@remote_file = remote_file
|
@remote_file = remote_file
|
||||||
|
|
||||||
@tftp_client = Rex::Proto::TFTP::Client.new(
|
@tftp_client = Rex::Proto::TFTP::Client.new(
|
||||||
"LocalHost" => @lhost,
|
"LocalHost" => @lhost,
|
||||||
"LocalPort" => @lport,
|
"LocalPort" => @lport,
|
||||||
"PeerHost" => rhost,
|
"PeerHost" => rhost,
|
||||||
"PeerPort" => rport,
|
"PeerPort" => rport,
|
||||||
"LocalFile" => @local_file,
|
"LocalFile" => @local_file,
|
||||||
"RemoteFile" => @remote_file,
|
"RemoteFile" => @remote_file,
|
||||||
"Mode" => mode,
|
"Mode" => mode,
|
||||||
"Action" => action.name.to_s.downcase.intern
|
"Action" => action.name.to_s.downcase.intern
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -124,28 +159,26 @@ class Metasploit3 < Msf::Auxiliary
|
||||||
while true
|
while true
|
||||||
if @tftp_client.complete
|
if @tftp_client.complete
|
||||||
print_status [rtarget,"TFTP transfer operation complete."].join
|
print_status [rtarget,"TFTP transfer operation complete."].join
|
||||||
if action.name == 'Download'
|
save_downloaded_file() if action.name == 'Download'
|
||||||
save_downloaded_file()
|
|
||||||
end
|
|
||||||
break
|
break
|
||||||
else
|
else
|
||||||
select(nil,nil,nil,1)
|
select(nil,nil,nil,1) # 1 second delays are just fine.
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_upload
|
def run_upload
|
||||||
print_status "Sending '#{file}' to #{rhost}:#{rport} as '#{remote_file}'"
|
print_status "Sending '#{file}' to #{rhost}:#{rport} as '#{remote_file}'"
|
||||||
@tftp_client.send_write_request { |msg| print_tftp_status(msg) }
|
ret = @tftp_client.send_write_request { |msg| print_tftp_status(msg) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_download
|
def run_download
|
||||||
print_status "Receiving '#{remote_file}' from #{rhost}:#{rport} as '#{file}'"
|
print_status "Receiving '#{remote_file}' from #{rhost}:#{rport} as '#{file}'"
|
||||||
@tftp_client.send_read_request { |msg| print_tftp_status(msg) }
|
ret = @tftp_client.send_read_request { |msg| print_tftp_status(msg) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def save_downloaded_file
|
def save_downloaded_file
|
||||||
print_status "Saving #{remote_file} as #{file}"
|
print_status "Saving #{remote_file} as '#{file}'"
|
||||||
fh = @tftp_client.recv_tempfile
|
fh = @tftp_client.recv_tempfile
|
||||||
data = File.open(fh,"rb") {|f| f.read f.stat.size} rescue nil
|
data = File.open(fh,"rb") {|f| f.read f.stat.size} rescue nil
|
||||||
if data and not data.empty?
|
if data and not data.empty?
|
||||||
|
|
Loading…
Reference in New Issue