public
Last active

ftps_implicit for Ruby

  • Download Gist
ftps_implicit.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
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

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.

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.