Connect to a FTPS server with mismatched server certificate using Net::FTPTLS - ruby

I am trying to connect via Net::FTPTLS to a Microsoft-based file server (IIS) which is configured to use FTP on port 22 and requires SSL.
I connect via:
require 'net/ftptls'
ftp = Net::FTPTLS.new()
ftp.connect('host.com', port_number)
ftp.login('Username', 'Password')
ftp.puttextfile('somefile.txt', 'where/to/save/somefile.txt')
ftp.close
Problem is, I get the following error:
hostname does not match the server certificate
It seems that I have to disable the openssl peer verification: OpenSSL::SSL::VERIFY_PEER should become OpenSSL::SSL::VERIFY_NONE.
Any ideas on how to monkey-patch the Net::FTPTLS class? Has anyone done this successfully?

Instead using Net::FTPTLS, use Ruby 2.4+ with the following code:
require 'net/ftp'
ftp = Net::FTP.new(nil, ssl: {:verify_mode => OpenSSL::SSL::VERIFY_NONE})
ftp.connect('host.com', port_number)
ftp.login('Username', 'Password')
ftp.puttextfile('somefile.txt', 'where/to/save/somefile.txt')
ftp.close

What I did, rather than monkeypatching ruby itself, was bring a copy of this into /lib of my project.
module Net
class FTPTLS < FTP
def connect(host, port=FTP_PORT)
#hostname = host
super
end
def login(user = "anonymous", params = {:password => nil, :acct => nil, :ignore_cert => false})
store = OpenSSL::X509::Store.new
store.set_default_paths
ctx = OpenSSL::SSL::SSLContext.new('SSLv23')
ctx.cert_store = store
ctx.verify_mode = params[:ignore_cert] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
ctx.key = nil
ctx.cert = nil
voidcmd("AUTH TLS")
#sock = OpenSSL::SSL::SSLSocket.new(#sock, ctx)
#sock.connect
#sock.post_connection_check(#hostname) unless params[:ignore_cert]
super(user, params[:password], params[:acct])
voidcmd("PBSZ 0")
end
end
end
I also cleaned up the param passing a bit. You would use this like so:
require 'ftptls' # Use my local version, not net/ftptls
#ftp_connection = Net::FTPTLS.new()
#ftp_connection.passive = true
#ftp_connection.connect(host, 21)
#ftp_connection.login('user', :password => 'pass', :ignore_cert => true)
HTH

This works fine for me. #ROR
ftp = Net::FTP.new("ftps.host.com", ftp_options)
open("where/is/your/file/somefile.txt") do |file_data|
ftp.putbinaryfile(file_data, 'where/to/save/somefile.txt')
end
ftp.puttextfile('somefile.txt', 'where/to/save/somefile.txt')
def ftp_options
{
port: FTP_PORT,
username: 'ftp_user',
password: 'password',
passive: true,
ssl: { verify_mode: 0 }
}
end
Remember that you have to provide ftps.hostname.com.

Related

Make FTPS connection in Ruby with double-bag-ftps gem

I am trying to get a native FTP connection work to an odd FTP server in ruby. It requires TLS and implicit SSL. I have a FileZilla client configured and working. Here's my code:
require 'double_bag_ftps'
DoubleBagFTPS.open(ftp_host, ftp_user, passwd, nil, DoubleBagFTPS::IMPLICIT, :verify_mode => OpenSSL::SSL::VERIFY_NONE) do |ftp|
...
files = ftp.list(file_path)
STDOUT.write files
end
I get the following runtime error when I run the above:
bunches of traceback lines
<path_to_gems>/double-bag-ftps-0.1.4/lib/double_bag_ftps.rb:160:in `initialize': wrong argument type nil (expected OpenSSL/SSL/CTX) (TypeError)
I can't seem to get anything out of the server with Ruby and the traditional net/ftp gem (various errors related to TLS/SSL problems). DoubleBagFTPS seems to be the most promising gem, but I still get an error. It may be the case that I am not calling the open function correctly. The only nil is the fourth parameter, but that's clearly spelled out in the DooubleBagFTPS example.
Can someone help?
Update
Per the suggestion, here's my new code
class MyFTP < Net::FTP
FTP_PORT = 990
def connect(host, port = FTP_PORT)
synchronize do
#host = host
#bare_sock = open_socket(host, port)
begin
ssl_sock = start_tls_session(Socket.tcp(host, port))
#sock = BufferedSSLSocket.new(ssl_sock, read_timeout: #read_timeout)
voidresp
if #private_data_connection
voidcmd("PBSZ 0")
voidcmd("PROT P")
end
rescue OpenSSL::SSL::SSLError, Net::OpenTimeout
#sock.close
raise
end
end
end
end
def ftp_options
{
username: 'user',
password: 'password',
ssl: true,
passive: true
}
end
MyFTP.open(ftp_host, ftp_options) do |ftp|
ftp.login
files = ftp.chdir(file_path)
files = ftp.list
STDOUT.write files
end
I'm still getting an error as follows:
---stack-trace---
<path_to_gem>/ruby/2.5.0/net/protocol.rb:52:in `connect': SSL_connect returned=1 errno=0 state=SSLv2/v3 read server hello A: unknown protocol (OpenSSL::SSL::SSLError)
So I got it working with regular old Net::FTP as follows:
def ftp_options
{
username: '<username>',
password: '<password>',
ssl: {
verify_mode: OpenSSL::SSL::VERIFY_NONE
}
}
end
Net::FTP.open(ftp_host, ftp_options) do |ftp|
ftp.login(ftp_options[:username], ftp_options[:password])
files = ftp.list
STDOUT.write files
puts "\n"
end
The one thing I don't understand is why I am forced to pass the username and password to the ftp.login method, since it's already defined in ftp_options, which was passed to Net::FTP.open(). As far as I can tell everything is set up correctly in ftp_options. For the particular server I'm connecting to, TLS/SSL is required, and that's working, so that parameter variable is being picked up... why not user/password?
Anyway, case closed for me at least. I can confirm that regular Net::FTP seems to work with at least one of these non-vanilla FTP servers requiring TLS and implicit SSL.

How to connect to FTP via SOCKS5 proxy with Ruby?

I'm trying to connect to FTP via SOCKS5 proxy using ruby's library Net::FTP. Documentation says to set env variable SOCKS_SERVER in order to connect through proxy (http://ruby-doc.org/stdlib-2.0.0/libdoc/net/ftp/rdoc/Net/FTP.html#method-i-connect), but it seems like it does not work.
Code I'm running is this:
irb(main):054:0> ftp = Net::FTP.new
=> #<Net::FTP:0x007efd08c73768 #mon_owner=nil, #mon_count=0, #mon_mutex=#<Thread::Mutex:0x007efd08c73718>, #binary=true, #passive=true, #debug_mode=false, #resume=false, #sock=#<Net::FTP::NullSocket:0x007efd08c736f0>, #logged_in=false, #open_timeout=nil, #read_timeout=60>
irb(main):056:0> ENV['SOCKS_SERVER'] = 'host:port'
=> "host:port"
irb(main):055:0> ftp.connect('test.rebex.net')
=> nil
irb(main):057:0> ftp.login('demo', 'password')
=> true
irb(main):058:0> ftp.ls
=> ["10-27-15 03:46PM <DIR> pub", "04-08-14 03:09PM 403 readme.txt"]
When I look to proxy logs I can not see any requests going through.
What I'm doing wrong or does anybody have an example how to achieve that?
If your on Windows computer you'll need to use dress_socks gem and Monkeypath:
$socks_server = '127.0.0.1'
$socks_port = '9090'
require 'dress_socks'
class Net::FTP
def open_socket(host, port) # :nodoc:
# puts "opening socket #{#host}:#{port}"
return DressSocks::Socket.new(#host, port,
socks_server: $socks_server, socks_port: $socks_port)
end
end

Connect to uncertified host via FTP over TLS/SSL

A vendor I grab a file from is changing from FTP to FTP over SSL.
I am trying to update my code from net/ftp to net/ftptls
The new host I need to connect to is not certified and my script reports back this error.
hostname was not match with the server certificate
The vendor will not fix this.
Looking at /usr/lib/ruby/1.8/net/ftptls.rb I thought it wouldn't be too hard to monkey-patch FTPTLS to ignore the untrusted host.
I tried changing verify_mode to OpenSSL::SSL::VERIFY_NONE and commenting out the post_connection_check` line.
neither worked.
Any thoughts on how to do this?
require 'socket'
require 'openssl'
require 'net/ftp'
module Net
class FTPTLS < FTP
def connect(host, port=FTP_PORT)
#hostname = host
super
end
def login(user = "anonymous", passwd = nil, acct = nil)
store = OpenSSL::X509::Store.new
store.set_default_paths
ctx = OpenSSL::SSL::SSLContext.new('SSLv23')
ctx.cert_store = store
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
ctx.key = nil
ctx.cert = nil
voidcmd("AUTH TLS")
#sock = OpenSSL::SSL::SSLSocket.new(#sock, ctx)
#sock.connect
#sock.post_connection_check(#hostname)
super(user, passwd, acct)
voidcmd("PBSZ 0")
end
end
end
This may be the world's slowest answer, but I ran across your question and it helped my fix it myself, so I wanted to post for posterity.
You were very close, you just need to also comment out #post_connection_check.
What I did, rather than monkeypatching ruby itself, was bring a copy of this into /lib of my project.
module Net
class FTPTLS < FTP
def connect(host, port=FTP_PORT)
#hostname = host
super
end
def login(user = "anonymous", params = {:password => nil, :acct => nil, :ignore_cert => false})
store = OpenSSL::X509::Store.new
store.set_default_paths
ctx = OpenSSL::SSL::SSLContext.new('SSLv23')
ctx.cert_store = store
ctx.verify_mode = params[:ignore_cert] ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER
ctx.key = nil
ctx.cert = nil
voidcmd("AUTH TLS")
#sock = OpenSSL::SSL::SSLSocket.new(#sock, ctx)
#sock.connect
#sock.post_connection_check(#hostname) unless params[:ignore_cert]
super(user, params[:password], params[:acct])
voidcmd("PBSZ 0")
end
end
end
I also cleaned up the param passing a bit. You would use this like so:
require 'ftptls' # Use my local version, not net/ftptls
#ftp_connection = Net::FTPTLS.new()
#ftp_connection.passive = true
#ftp_connection.connect(host, 21)
#ftp_connection.login('user', :password => 'pass', :ignore_cert => true)
HTH
I know this is probably too late for Poul but I found the double-bag-ftps gem to be sufficient and easy to use when I had to do something similar.

Ruby - remote file download ...timeout?

Excuse the tabs. I'm trying to download a file from remote to local and I keep getting one back that is exactly 310 bytes (regardless of what file I choose to download). I tried setting the timeout to 0, but this isn't working. What am I doing wrong? Thanks!
#downloadUrl = 'https://username:password#api.net'
Net::HTTP.start(#downloadUrl) do |http|
response = http.get('/file.ext')
open('/Users/me/file.ext', "wb", :read_timeout=>0) do |file|
file.write(response.body)
end
end
EDIT: I don't want to use httpclient, I want to use standard net/http. I am almost there, but I keep getting initialize': getaddrinfo: nodename nor servname provided, or not known (SocketError) thrown at Net::HTTP.start(url.path). But when I remote "https", I get ECONNREFUSED. ...Getting closer?
url = URI.parse('https://api.net/file.ext')
#request = Net::HTTP.start(url.path)
#request.basic_auth 'username', 'password'
sock = Net::HTTP.new(url.host, 443)
sock.use_ssl = true
sock.ssl_version='SSLv3'
sock.start do |http|
response = http.get(#request)
open('/Users/me/file.ext', "wb", :read_timeout=>0) do |file|
file.write(response.body)
end
end
Using httpclient is much simpler when accessing via SSL.
gem install httpclient
I haven't tried this out, but this should work for you. Here is the rdoc.
require 'httpclient'
domain = "https://api.net/file.ext"
username = "username"
password = "password"
clnt = HTTPClient.new
clnt.set_auth(domain, username, password)
res = clnt.get_content(https_url)
You can refer to the "writing a binary file in ruby" question for saving your response to a file.
Updated Using net/http
You should be doing Net::HTTP.start(url.host, url.port) instead of Net:HTTP.start(url.path). See RDoc for Net::HTTP.start.
url = URI.parse("https://api.net/file.ext")
download_file = opne("/Users/me/file.ext", "wb")
request = Net::HTTP.start(url.host, url.port)
# .. set basic auth, verify peer etc
begin
request.request_get(url.path) do |resp|
resp.read_body { |segment| download_file.write(segment) }
end
ensure
download_file.close
end

How to enable SSL for a standalone Sinatra app?

I want to write a quick server app in Sinatra. It has to be self-contained (i.e. not use apache/nginx/passenger) but also has to support SSL.
Is there an easy way to enable SSL support for Sinatra (using WEBRick for example)?
To do this with MRI ruby, use the following monkeypatch:
sinatra_ssl.rb:
require 'webrick/https'
module Sinatra
class Application
def self.run!
certificate_content = File.open(ssl_certificate).read
key_content = File.open(ssl_key).read
server_options = {
:Host => bind,
:Port => port,
:SSLEnable => true,
:SSLCertificate => OpenSSL::X509::Certificate.new(certificate_content),
:SSLPrivateKey => OpenSSL::PKey::RSA.new(key_content)
}
Rack::Handler::WEBrick.run self, server_options do |server|
[:INT, :TERM].each { |sig| trap(sig) { server.stop } }
server.threaded = settings.threaded if server.respond_to? :threaded=
set :running, true
end
end
end
end
Then, in your standalone application:
app.rb
require 'sinatra'
require 'sinatra_ssl'
set :port, 8443
set :ssl_certificate, "server.crt"
set :ssl_key, "server.key"
get "/" do
"Hello world!"
end
Use JRuby interpreter + jetty-rackup gem (http://github.com/geekq/jetty-rackup)
Edit jetty-rackup file in the jetty-rackup gem and add a SslSocketConnector, some code to help you:
security_connector = Jetty::Security::SslSocketConnector.new
security_connector.set_acceptors(config[:acceptor_size])
security_connector.port = config[:port]
security_connector.confidential_port = config[:port]
security_connector.keystore = keystore
security_connector.password = config[:password]
security_connector.key_password = config[:key_password].nil? ? config[:password] : config[:key_password]
security_connector.truststore = truststore
security_connector.trust_password = config[:trust_pasword].nil? ? config[:password] : config[:trust_pasword]
server.add_connector(security_connector)
Sample config:
# Config
:acceptor_size: 10
:ssl: true
:keystore: keystore.jks
:password: your_pass
# :key_password: your_pass # if different
# :truststore: truststore.jks # if different
# :trust_pasword: your_pass # if different
Generating keystore.jks : http://docs.codehaus.org/display/JETTY/How+to+configure+SSL

Resources