Skip to content

Commit

Permalink
[ruby/net-ftp] Close the passive connection data socket if there is a…
Browse files Browse the repository at this point in the history
…n error setting up the transfer

Previously, the connection leaked in this case.  This uses
begin/ensure and checking for an error in the ensure block.

An alternative approach would be to not even perform the
connection until after the RETR (or other) command has been
sent.  However, I'm not sure all FTP servers support that.
The current behavior is:

* Send (PASV/EPSV)
* Connect to the host/port returned in 227/229 reply
* Send (RETR/other command)

Changing it to connect after the RETR could break things.
FTP servers might expect that the client has already
connected before sending the RETR.  The alternative
approach is more likely to introduce backwards compatibility
issues, compared to the begin/ensure approach taken here.

Fixes Ruby Bug 17027

ruby/net-ftp@6e8535f076
  • Loading branch information
jeremyevans authored and hsbt committed Apr 27, 2021
1 parent a86c6cb commit 990baec
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 12 deletions.
24 changes: 14 additions & 10 deletions lib/net/ftp.rb
Expand Up @@ -547,18 +547,22 @@ def makepasv # :nodoc:
def transfercmd(cmd, rest_offset = nil) # :nodoc:
if @passive
host, port = makepasv
conn = open_socket(host, port)
if @resume and rest_offset
resp = sendcmd("REST " + rest_offset.to_s)
if !resp.start_with?("3")
begin
conn = open_socket(host, port)
if @resume and rest_offset
resp = sendcmd("REST " + rest_offset.to_s)
if !resp.start_with?("3")
raise FTPReplyError, resp
end
end
resp = sendcmd(cmd)
# skip 2XX for some ftp servers
resp = getresp if resp.start_with?("2")
if !resp.start_with?("1")
raise FTPReplyError, resp
end
end
resp = sendcmd(cmd)
# skip 2XX for some ftp servers
resp = getresp if resp.start_with?("2")
if !resp.start_with?("1")
raise FTPReplyError, resp
ensure
conn.close if conn && $!
end
else
sock = makeport
Expand Down
39 changes: 37 additions & 2 deletions test/net/ftp/test_ftp.rb
Expand Up @@ -882,6 +882,41 @@ def test_getbinaryfile_with_filename_and_block
end
end

def test_getbinaryfile_error
commands = []
binary_data = ""
server = create_ftp_server { |sock|
sock.print("220 (test_ftp).\r\n")
commands.push(sock.gets)
sock.print("331 Please specify the password.\r\n")
commands.push(sock.gets)
sock.print("230 Login successful.\r\n")
commands.push(sock.gets)
sock.print("200 Switching to Binary mode.\r\n")
line = sock.gets
commands.push(line)
sock.print("450 No Dice\r\n")
}
begin
begin
ftp = Net::FTP.new
ftp.passive = true
ftp.read_timeout *= 5 if defined?(RubyVM::MJIT) && RubyVM::MJIT.enabled? # for --jit-wait
ftp.connect(SERVER_ADDR, server.port)
ftp.login
assert_match(/\AUSER /, commands.shift)
assert_match(/\APASS /, commands.shift)
assert_equal("TYPE I\r\n", commands.shift)
assert_raise(Net::FTPTempError) {ftp.getbinaryfile("foo", nil)}
assert_match(/\A(PASV|EPSV)\r\n/, commands.shift)
ensure
ftp.close if ftp
end
ensure
server.close
end
end

def test_storbinary
commands = []
binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3
Expand Down Expand Up @@ -1935,7 +1970,7 @@ def test_active_private_data_connection
assert_equal(nil, commands.shift)
# FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
# See https://github.com/openssl/openssl/pull/5967 for details.
if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/
if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/
assert_equal(true, session_reused_for_data_connection)
end
ensure
Expand Down Expand Up @@ -2019,7 +2054,7 @@ def test_passive_private_data_connection
assert_equal("RETR foo\r\n", commands.shift)
assert_equal(nil, commands.shift)
# FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h.
if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/
if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/
assert_equal(true, session_reused_for_data_connection)
end
ensure
Expand Down

0 comments on commit 990baec

Please sign in to comment.