Skip to content

Instantly share code, notes, and snippets.

@dcparker
Created March 25, 2009 18:47
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save dcparker/85632 to your computer and use it in GitHub Desktop.
Save dcparker/85632 to your computer and use it in GitHub Desktop.
ftps_implicit for Ruby
require 'socket'
require 'openssl'
require 'net/ftp'
class Net::FTPS < Net::FTP
end
class Net::FTPS::Implicit < Net::FTP
FTP_PORT = 990
def initialize(host=nil, user=nil, passwd=nil, acct=nil)
super
@passive = true
@binary = false
@debug_mode = true
@data_protection = 'P'
@data_protected = false
end
attr_accessor :data_protection
def open_socket(host, port, data_socket=false)
tcpsock = if defined? SOCKSsocket and ENV["SOCKS_SERVER"]
@passive = true
SOCKSsocket.open(host, port)
else
TCPSocket.new(host, port)
end
if !data_socket || @data_protection == 'P'
ssl_context = OpenSSL::SSL::SSLContext.new('SSLv23')
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
ssl_context.key = nil
ssl_context.cert = nil
ssl_context.timeout = 10
sock = OpenSSL::SSL::SSLSocket.new(tcpsock, ssl_context)
sock.connect
else
sock = tcpsock
end
return sock
end
private :open_socket
def connect(host, port=FTP_PORT)
@sock = open_socket(host, port)
mon_initialize
getresp
at_exit {
if @sock && !@sock.closed?
voidcmd("ABOR") rescue EOFError
voidcmd("QUIT") rescue EOFError
close
end
}
end
def abort
voidcmd("ABOR") rescue EOFError
end
def quit
voidcmd("QUIT") rescue EOFError
end
def close
@sock.close # SSL
@sock.io.close # TCP
end
def retrbinary(cmd, blocksize, rest_offset = nil) # :yield: data
synchronize do
voidcmd("TYPE I")
conn = transfercmd(cmd, rest_offset)
data = get_data(conn,blocksize)
yield(data)
voidresp
end
end
def get_data(sock,blocksize=1024)
timeout = 10
starttime = Time.now
buffer = ''
timeouts = 0
catch :done do
loop do
event = select([sock],nil,nil,0.5)
if event.nil? # nil would be a timeout, we'd do nothing and start loop over. Of course here we really have no timeout...
timeouts += 0.5
break if timeouts > timeout
else
event[0].each do |sock| # Iterate through all sockets that have pending activity
if sock.eof? # Socket's been closed by the client
throw :done
else
buffer << sock.readpartial(blocksize)
if block_given? # we're in line-by-line mode
lines = buffer.split(/\r?\n/)
buffer = buffer =~ /\n$/ ? '' : lines.pop
lines.each do |line|
yield(line)
end
end
end
end
end
end
end
sock.close
buffer
end
def retrlines(cmd) # :yield: line
synchronize do
voidcmd("TYPE A")
voidcmd("STRU F")
voidcmd("MODE S")
conn = transfercmd(cmd)
get_data(conn) do |line|
yield(line)
end
getresp
end
end
#
# Puts the connection into binary (image) mode, issues the given server-side
# command (such as "STOR myfile"), and sends the contents of the file named
# +file+ to the server. If the optional block is given, it also passes it
# the data, in chunks of +blocksize+ characters.
#
def storbinary(cmd, file, blocksize, rest_offset = nil, &block) # :yield: data
if rest_offset
file.seek(rest_offset, IO::SEEK_SET)
end
synchronize do
voidcmd("TYPE I")
conn = transfercmd(cmd, rest_offset)
loop do
buf = file.read(blocksize)
break if buf == nil
conn.write(buf)
yield(buf) if block
end
conn.close # closes the SSL
conn.io.close # closes the TCP below it
voidresp
end
end
#
# Puts the connection into ASCII (text) mode, issues the given server-side
# command (such as "STOR myfile"), and sends the contents of the file
# named +file+ to the server, one line at a time. If the optional block is
# given, it also passes it the lines.
#
def storlines(cmd, file, &block) # :yield: line
synchronize do
voidcmd("TYPE A")
conn = transfercmd(cmd)
loop do
buf = file.gets
break if buf == nil
if buf[-2, 2] != CRLF
buf = buf.chomp + CRLF
end
conn.write(buf)
yield(buf) if block
end
conn.close # closes the SSL
conn.io.close # closes the TCP below it
voidresp
end
end
def transfercmd(cmd, rest_offset=nil)
unless @data_protected
voidcmd('PBSZ 0')
sendcmd("PROT #{@data_protection}")
@data_protected = true
end
if @passive
host, port = makepasv
if @resume and rest_offset
resp = sendcmd("REST " + rest_offset.to_s)
if resp[0] != ?3
raise FTPReplyError, resp
end
end
putline(cmd)
conn = open_socket(host, port, true)
resp = getresp # Should be a 150 response
if resp[0] != ?1
raise FTPReplyError, resp
end
else
sock = makeport
if @resume and rest_offset
resp = sendcmd("REST " + rest_offset.to_s)
if resp[0] != ?3
raise FTPReplyError, resp
end
end
resp = sendcmd(cmd)
if resp[0] != ?1
raise FTPReplyError, resp
end
conn = sock.accept
sock.close
end
return conn
end
private :transfercmd
end
@tomrossi7
Copy link

I'm not sure if this will help someone else, but In order to get my connection to list a directory, I had to comment out lines 116 and 117. I was getting the following error:

> ftp.nlst
put: TYPE A
get: 200 Type set to A.
put: STRU F
get: 202 Command not implemented, superfluous at this site.
put: MODE S
get: 500 'MODE': command not understood.
Net::FTPPermError: 500 'MODE': command not understood.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment