Created
March 25, 2009 18:47
-
-
Save dcparker/85632 to your computer and use it in GitHub Desktop.
ftps_implicit for Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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: