double-bag-ftps 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
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,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec) do |t|
5
+ t.rspec_opts = %w[--color]
6
+ end
7
+
8
+ task :default => :spec
@@ -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
@@ -0,0 +1,6 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'double_bag_ftps'
3
+
4
+ HOST = 'ftp.secureftp-test.com'
5
+ USR = 'test'
6
+ PASSWD = 'test'
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: []