double-bag-ftps 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/LICENSE +20 -0
- data/README.md +65 -0
- data/Rakefile +8 -0
- data/double_bag_ftps.gemspec +18 -0
- data/lib/double_bag_ftps.rb +164 -0
- data/spec/double_bag_ftps_spec.rb +72 -0
- data/spec/spec_helper.rb +6 -0
- metadata +74 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Bryan Nix
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
DoubleBagFTPS
|
2
|
+
=============
|
3
|
+
|
4
|
+
DoubleBagFTPS extends the core Net::FTP class to provide implicit and explicit FTPS support.
|
5
|
+
|
6
|
+
Install
|
7
|
+
-------
|
8
|
+
|
9
|
+
$ [sudo] gem install double-bag-ftps
|
10
|
+
|
11
|
+
**Note**: Your Ruby installation must have OpenSSL support.
|
12
|
+
|
13
|
+
Usage
|
14
|
+
-----
|
15
|
+
require 'double_bag_ftps'
|
16
|
+
|
17
|
+
Example 1:
|
18
|
+
|
19
|
+
# Connect to a host using explicit FTPS and do not verify the host's cert
|
20
|
+
ftps = DoubleBagFTPS.new
|
21
|
+
ftps.ssl_context = DoubleBagFTPS.create_ssl_context(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
|
22
|
+
ftps.connect('some host')
|
23
|
+
ftps.login('usr', 'passwd')
|
24
|
+
|
25
|
+
Example 2:
|
26
|
+
|
27
|
+
DoubleBagFTPS.open('host', 'usr', 'passwd', nil, DoubleBagFTPS::IMPLICIT) do |ftps|
|
28
|
+
...
|
29
|
+
end
|
30
|
+
|
31
|
+
Interface
|
32
|
+
---------
|
33
|
+
|
34
|
+
# Constants used for setting FTPS mode
|
35
|
+
DoubleBagFTPS::EXPLICIT
|
36
|
+
DoubleBagFTPS::IMPLICIT
|
37
|
+
|
38
|
+
DoubleBagFTPS.new(host = nil, user = nil, passwd = nil, acct = nil, ftps_mode = EXPLICIT, ssl_context_params = {})
|
39
|
+
DoubleBagFTPS.open(host, user = nil, passwd = nil, acct = nil, ftps_mode = EXPLICIT, ssl_context_params = {})
|
40
|
+
|
41
|
+
# Returns an OpenSSL::SSL::SSLContext using params to set set the corresponding SSLContext attributes.
|
42
|
+
DoubleBagFTPS.create_ssl_context(params = {})
|
43
|
+
|
44
|
+
# Set the FTPS mode to implicit (DoubleBagFTPS::IMPLICIT) or explicit (DoubleBagFTPS::EXPLICIT).
|
45
|
+
# The default FTPS mode is explicit.
|
46
|
+
ftps_mode=(ftps_mode)
|
47
|
+
|
48
|
+
# Same as Net::FTP.connect, but will use port 990 when using implicit FTPS and a port is not specified.
|
49
|
+
connect(host, port = ftps_implicit? ? IMPLICIT_PORT : FTP_PORT)
|
50
|
+
|
51
|
+
# Same as Net::FTP.login, but with optional auth param to control the value that is sent with the AUTH command.
|
52
|
+
login(user = 'anonymous', passwd = nil, acct = nil, auth = 'TLS')
|
53
|
+
|
54
|
+
ftps_explicit?
|
55
|
+
ftps_implicit?
|
56
|
+
|
57
|
+
More Information
|
58
|
+
----------------
|
59
|
+
|
60
|
+
* [Net::FTP RDoc](http://ruby-doc.org/stdlib/libdoc/net/ftp/rdoc/index.html)
|
61
|
+
* [OpenSSL RDoc](http://ruby-doc.org/stdlib/libdoc/openssl/rdoc/index.html)
|
62
|
+
|
63
|
+
License
|
64
|
+
-------
|
65
|
+
Copyright © 2011, Bryan Nix. DoubleBagFTPS is released under the MIT license. See LICENSE file for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "double-bag-ftps"
|
6
|
+
s.version = "0.1.0"
|
7
|
+
s.author = "Bryan Nix"
|
8
|
+
s.homepage = "https://github.com/bnix/double-bag-ftps"
|
9
|
+
s.summary = "Provides a child class of Net::FTP to support implicit and explicit FTPS."
|
10
|
+
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
s.require_paths = ["lib"]
|
15
|
+
|
16
|
+
s.add_development_dependency "bundler"
|
17
|
+
s.add_development_dependency "rspec"
|
18
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'net/ftp'
|
2
|
+
begin
|
3
|
+
require 'openssl'
|
4
|
+
rescue LoadError
|
5
|
+
end
|
6
|
+
|
7
|
+
class DoubleBagFTPS < Net::FTP
|
8
|
+
EXPLICIT = :explicit
|
9
|
+
IMPLICIT = :implicit
|
10
|
+
IMPLICIT_PORT = 990
|
11
|
+
|
12
|
+
# The form of FTPS that should be used. Either EXPLICIT or IMPLICIT.
|
13
|
+
# Defaults to EXPLICIT.
|
14
|
+
attr_reader :ftps_mode
|
15
|
+
|
16
|
+
# The OpenSSL::SSL::SSLContext to use for creating all OpenSSL::SSL::SSLSocket objects.
|
17
|
+
attr_accessor :ssl_context
|
18
|
+
|
19
|
+
def initialize(host = nil, user = nil, passwd = nil, acct = nil, ftps_mode = EXPLICIT, ssl_context_params = {})
|
20
|
+
raise ArgumentError unless valid_ftps_mode?(ftps_mode)
|
21
|
+
@ftps_mode = ftps_mode
|
22
|
+
@ssl_context = DoubleBagFTPS.create_ssl_context(ssl_context_params)
|
23
|
+
super(host, user, passwd, acct)
|
24
|
+
end
|
25
|
+
|
26
|
+
def DoubleBagFTPS.open(host, user = nil, passwd = nil, acct = nil, ftps_mode = EXPLICIT, ssl_context_params = {})
|
27
|
+
if block_given?
|
28
|
+
ftps = new(host, user, passwd, acct, ftps_mode, ssl_context_params)
|
29
|
+
begin
|
30
|
+
yield ftps
|
31
|
+
ensure
|
32
|
+
ftps.close
|
33
|
+
end
|
34
|
+
else
|
35
|
+
new(host, user, passwd, acct, ftps_mode, ssl_context_params)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Allow @ftps_mode to be set when @sock is not connected
|
41
|
+
#
|
42
|
+
def ftps_mode=(ftps_mode)
|
43
|
+
# Ruby 1.8.7/1.9.2 compatible check
|
44
|
+
if (defined?(NullSocket) && @sock.kind_of?(NullSocket)) || @sock.nil? || @sock.closed?
|
45
|
+
raise ArgumentError unless valid_ftps_mode?(ftps_mode)
|
46
|
+
@ftps_mode = ftps_mode
|
47
|
+
else
|
48
|
+
raise 'Cannot set ftps_mode while connected'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Establishes the command channel.
|
54
|
+
# Override parent to record host name for verification, and allow default implicit port.
|
55
|
+
#
|
56
|
+
def connect(host, port = ftps_implicit? ? IMPLICIT_PORT : FTP_PORT)
|
57
|
+
@hostname = host
|
58
|
+
super
|
59
|
+
end
|
60
|
+
|
61
|
+
def login(user = 'anonymous', passwd = nil, acct = nil, auth = 'TLS')
|
62
|
+
if ftps_explicit?
|
63
|
+
synchronize do
|
64
|
+
sendcmd('AUTH ' + auth) # Set the security mechanism
|
65
|
+
@sock = ssl_socket(@sock)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
super(user, passwd, acct)
|
70
|
+
voidcmd('PBSZ 0') # The expected value for Protection Buffer Size (PBSZ) is 0 for TLS/SSL
|
71
|
+
voidcmd('PROT P') # Set data channel protection level to Private
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Override parent to allow an OpenSSL::SSL::SSLSocket to be returned
|
76
|
+
# when using implicit FTPS
|
77
|
+
#
|
78
|
+
def open_socket(host, port, defer_implicit_ssl = false)
|
79
|
+
if defined? SOCKSSocket and ENV["SOCKS_SERVER"]
|
80
|
+
@passive = true
|
81
|
+
sock = SOCKSSocket.open(host, port)
|
82
|
+
else
|
83
|
+
sock = TCPSocket.open(host, port)
|
84
|
+
end
|
85
|
+
return (!defer_implicit_ssl && ftps_implicit?) ? ssl_socket(sock) : sock
|
86
|
+
end
|
87
|
+
private :open_socket
|
88
|
+
|
89
|
+
#
|
90
|
+
# Override parent to support ssl sockets
|
91
|
+
#
|
92
|
+
def transfercmd(cmd, rest_offset = nil)
|
93
|
+
if @passive
|
94
|
+
host, port = makepasv
|
95
|
+
|
96
|
+
if @resume and rest_offset
|
97
|
+
resp = sendcmd('REST ' + rest_offset.to_s)
|
98
|
+
if resp[0] != ?3
|
99
|
+
raise FTPReplyError, resp
|
100
|
+
end
|
101
|
+
end
|
102
|
+
conn = open_socket(host, port, true)
|
103
|
+
resp = sendcmd(cmd)
|
104
|
+
# skip 2XX for some ftp servers
|
105
|
+
resp = getresp if resp[0] == ?2
|
106
|
+
if resp[0] != ?1
|
107
|
+
raise FTPReplyError, resp
|
108
|
+
end
|
109
|
+
conn = ssl_socket(conn) # SSL connection now possible after cmd sent
|
110
|
+
else
|
111
|
+
sock = makeport
|
112
|
+
if @resume and rest_offset
|
113
|
+
resp = sendcmd('REST ' + rest_offset.to_s)
|
114
|
+
if resp[0] != ?3
|
115
|
+
raise FTPReplyError, resp
|
116
|
+
end
|
117
|
+
end
|
118
|
+
resp = sendcmd(cmd)
|
119
|
+
# skip 2XX for some ftp servers
|
120
|
+
resp = getresp if resp[0] == ?2
|
121
|
+
if resp[0] != ?1
|
122
|
+
raise FTPReplyError, resp
|
123
|
+
end
|
124
|
+
|
125
|
+
temp_ssl_sock = ssl_socket(sock)
|
126
|
+
conn = temp_ssl_sock.accept
|
127
|
+
temp_ssl_sock.close
|
128
|
+
end
|
129
|
+
return conn
|
130
|
+
end
|
131
|
+
private :transfercmd
|
132
|
+
|
133
|
+
def ftps_explicit?; @ftps_mode == EXPLICIT end
|
134
|
+
def ftps_implicit?; @ftps_mode == IMPLICIT end
|
135
|
+
|
136
|
+
def valid_ftps_mode?(mode)
|
137
|
+
mode == EXPLICIT || mode == IMPLICIT
|
138
|
+
end
|
139
|
+
private :valid_ftps_mode?
|
140
|
+
|
141
|
+
#
|
142
|
+
# Returns a connected OpenSSL::SSL::SSLSocket
|
143
|
+
#
|
144
|
+
def ssl_socket(sock)
|
145
|
+
raise 'SSL extension not installed' unless defined?(OpenSSL)
|
146
|
+
sock = OpenSSL::SSL::SSLSocket.new(sock, @ssl_context)
|
147
|
+
sock.sync_close = true
|
148
|
+
sock.connect
|
149
|
+
print "get: #{sock.peer_cert.to_text}" if @debug_mode
|
150
|
+
unless @ssl_context.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
151
|
+
sock.post_connection_check(@hostname)
|
152
|
+
end
|
153
|
+
return sock
|
154
|
+
end
|
155
|
+
private :ssl_socket
|
156
|
+
|
157
|
+
def DoubleBagFTPS.create_ssl_context(params = {})
|
158
|
+
raise 'SSL extension not installed' unless defined?(OpenSSL)
|
159
|
+
context = OpenSSL::SSL::SSLContext.new
|
160
|
+
context.set_params(params)
|
161
|
+
return context
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
shared_examples_for "DoubleBagFTPS" do
|
4
|
+
it "connects to a remote host" do
|
5
|
+
lambda {@ftp.connect(HOST)}.should_not raise_error
|
6
|
+
end
|
7
|
+
|
8
|
+
it "logs in with a user name and password" do
|
9
|
+
@ftp.connect(HOST)
|
10
|
+
lambda {@ftp.login(USR, PASSWD)}.should_not raise_error
|
11
|
+
end
|
12
|
+
|
13
|
+
it "can open a secure data channel" do
|
14
|
+
@ftp.connect(HOST)
|
15
|
+
@ftp.login(USR, PASSWD)
|
16
|
+
@ftp.send(:transfercmd, 'nlst').should be_an_instance_of OpenSSL::SSL::SSLSocket
|
17
|
+
end
|
18
|
+
|
19
|
+
it "prevents setting the FTPS mode while connected" do
|
20
|
+
@ftp.connect(HOST)
|
21
|
+
lambda {@ftp.ftps_mode = 'dummy value'}.should raise_error
|
22
|
+
end
|
23
|
+
|
24
|
+
it "prevents setting the FTPS mode to an unrecognized value" do
|
25
|
+
lambda {@ftp.ftps_mode = 'dummy value'}.should raise_error
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
describe DoubleBagFTPS do
|
31
|
+
context "implicit" do
|
32
|
+
before(:each) do
|
33
|
+
@ftp = DoubleBagFTPS.new
|
34
|
+
@ftp.ftps_mode = DoubleBagFTPS::IMPLICIT
|
35
|
+
@ftp.passive = true
|
36
|
+
@ftp.ssl_context = DoubleBagFTPS.create_ssl_context(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
|
37
|
+
end
|
38
|
+
|
39
|
+
after(:each) do
|
40
|
+
@ftp.close unless @ftp.welcome.nil?
|
41
|
+
end
|
42
|
+
|
43
|
+
it "uses an SSLSocket when first connected" do
|
44
|
+
@ftp.connect(HOST)
|
45
|
+
@ftp.instance_eval {def socket; @sock; end}
|
46
|
+
@ftp.socket.should be_an_instance_of OpenSSL::SSL::SSLSocket
|
47
|
+
end
|
48
|
+
|
49
|
+
it_should_behave_like "DoubleBagFTPS"
|
50
|
+
end
|
51
|
+
|
52
|
+
context "explicit" do
|
53
|
+
before(:each) do
|
54
|
+
@ftp = DoubleBagFTPS.new
|
55
|
+
@ftp.ftps_mode = DoubleBagFTPS::EXPLICIT
|
56
|
+
@ftp.passive = true
|
57
|
+
@ftp.ssl_context = DoubleBagFTPS.create_ssl_context(:verify_mode => OpenSSL::SSL::VERIFY_NONE)
|
58
|
+
end
|
59
|
+
|
60
|
+
after(:each) do
|
61
|
+
@ftp.close unless @ftp.welcome.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
it "does not use an SSLSocket when first connected" do
|
65
|
+
@ftp.connect(HOST)
|
66
|
+
@ftp.instance_eval {def socket; @sock; end}
|
67
|
+
@ftp.socket.should_not be_an_instance_of OpenSSL::SSL::SSLSocket
|
68
|
+
end
|
69
|
+
|
70
|
+
it_should_behave_like "DoubleBagFTPS"
|
71
|
+
end
|
72
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: double-bag-ftps
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Bryan Nix
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-10-03 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &75184820 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *75184820
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &75184600 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *75184600
|
36
|
+
description:
|
37
|
+
email:
|
38
|
+
executables: []
|
39
|
+
extensions: []
|
40
|
+
extra_rdoc_files: []
|
41
|
+
files:
|
42
|
+
- .gitignore
|
43
|
+
- LICENSE
|
44
|
+
- README.md
|
45
|
+
- Rakefile
|
46
|
+
- double_bag_ftps.gemspec
|
47
|
+
- lib/double_bag_ftps.rb
|
48
|
+
- spec/double_bag_ftps_spec.rb
|
49
|
+
- spec/spec_helper.rb
|
50
|
+
homepage: https://github.com/bnix/double-bag-ftps
|
51
|
+
licenses: []
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ! '>='
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
requirements: []
|
69
|
+
rubyforge_project:
|
70
|
+
rubygems_version: 1.8.10
|
71
|
+
signing_key:
|
72
|
+
specification_version: 3
|
73
|
+
summary: Provides a child class of Net::FTP to support implicit and explicit FTPS.
|
74
|
+
test_files: []
|