ftpd 0.8.0 → 0.9.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.
- checksums.yaml +7 -0
- data/Changelog.md +8 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +34 -4
- data/README.md +64 -40
- data/VERSION +1 -1
- data/doc/rfc-compliance.md +2 -2
- data/examples/example.rb +14 -6
- data/examples/example_spec.rb +93 -0
- data/features/ftp_server/eprt.feature +54 -0
- data/features/ftp_server/epsv.feature +36 -0
- data/features/ftp_server/get_ipv6.feature +43 -0
- data/features/ftp_server/list.feature +9 -0
- data/features/ftp_server/name_list.feature +9 -0
- data/features/ftp_server/pasv.feature +23 -0
- data/features/ftp_server/port.feature +7 -1
- data/features/ftp_server/step_definitions/test_server.rb +4 -0
- data/features/step_definitions/connect.rb +1 -1
- data/features/step_definitions/error_replies.rb +8 -0
- data/features/support/test_server.rb +2 -1
- data/ftpd.gemspec +16 -7
- data/lib/ftpd.rb +2 -0
- data/lib/ftpd/list_path.rb +28 -0
- data/lib/ftpd/protocols.rb +60 -0
- data/lib/ftpd/session.rb +76 -10
- data/lib/ftpd/tls_server.rb +19 -1
- data/spec/list_path_spec.rb +21 -0
- data/spec/protocols_spec.rb +139 -0
- data/spec/spec_helper.rb +1 -0
- metadata +34 -48
@@ -0,0 +1,36 @@
|
|
1
|
+
Feature: EPSV
|
2
|
+
|
3
|
+
As a programmer
|
4
|
+
I want good error messages
|
5
|
+
So that I can correct problems
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the test server is bound to "::"
|
9
|
+
And the test server is started
|
10
|
+
|
11
|
+
Scenario: No argument
|
12
|
+
Given a successful login
|
13
|
+
Then the client successfully sends "EPSV"
|
14
|
+
|
15
|
+
Scenario: Explicit IPV4
|
16
|
+
Given a successful login
|
17
|
+
Then the client successfully sends "EPSV 1"
|
18
|
+
|
19
|
+
Scenario: Explicit IPV6
|
20
|
+
Given a successful login
|
21
|
+
Then the client successfully sends "EPSV 2"
|
22
|
+
|
23
|
+
Scenario: After "EPSV ALL"
|
24
|
+
Given a successful login
|
25
|
+
Given the client successfully sends "EPSV ALL"
|
26
|
+
Then the client successfully sends "EPSV"
|
27
|
+
|
28
|
+
Scenario: Not logged in
|
29
|
+
Given a successful connection
|
30
|
+
When the client sends "EPSV"
|
31
|
+
Then the server returns a not logged in error
|
32
|
+
|
33
|
+
Scenario: Unknown network protocol
|
34
|
+
Given a successful login
|
35
|
+
When the client sends "EPSV 99"
|
36
|
+
Then the server returns a network protocol not supported error
|
@@ -0,0 +1,43 @@
|
|
1
|
+
Feature: Get IPV6
|
2
|
+
|
3
|
+
As a client
|
4
|
+
I want to get a file
|
5
|
+
So that I have it on my computer
|
6
|
+
|
7
|
+
Scenario: Active
|
8
|
+
Given the test server is bound to "::1"
|
9
|
+
And the test server is started
|
10
|
+
And a successful login
|
11
|
+
And the server has file "ascii_unix"
|
12
|
+
And the client is in active mode
|
13
|
+
When the client successfully gets text "ascii_unix"
|
14
|
+
Then the local file "ascii_unix" should match the remote file
|
15
|
+
|
16
|
+
Scenario: Passive
|
17
|
+
Given the test server is bound to "::1"
|
18
|
+
And the test server is started
|
19
|
+
And a successful login
|
20
|
+
And the server has file "ascii_unix"
|
21
|
+
And the client is in passive mode
|
22
|
+
When the client successfully gets text "ascii_unix"
|
23
|
+
Then the local file "ascii_unix" should match the remote file
|
24
|
+
|
25
|
+
Scenario: Active, TLS
|
26
|
+
Given the test server is bound to "::1"
|
27
|
+
And the test server has TLS mode "explicit"
|
28
|
+
And the test server is started
|
29
|
+
And a successful login
|
30
|
+
And the server has file "ascii_unix"
|
31
|
+
And the client is in active mode
|
32
|
+
When the client successfully gets text "ascii_unix"
|
33
|
+
Then the local file "ascii_unix" should match the remote file
|
34
|
+
|
35
|
+
Scenario: Passive, TLS
|
36
|
+
Given the test server is bound to "::1"
|
37
|
+
And the test server has TLS mode "explicit"
|
38
|
+
And the test server is started
|
39
|
+
And a successful login
|
40
|
+
And the server has file "ascii_unix"
|
41
|
+
And the client is in passive mode
|
42
|
+
When the client successfully gets text "ascii_unix"
|
43
|
+
Then the local file "ascii_unix" should match the remote file
|
@@ -68,6 +68,15 @@ Feature: List
|
|
68
68
|
And the file list should contain "foo"
|
69
69
|
And the file list should contain "bar"
|
70
70
|
|
71
|
+
Scenario: -a
|
72
|
+
Given a successful login
|
73
|
+
And the server has file "foo"
|
74
|
+
And the server has file "bar"
|
75
|
+
When the client successfully lists the directory "-a"
|
76
|
+
Then the file list should be in long form
|
77
|
+
And the file list should contain "foo"
|
78
|
+
And the file list should contain "bar"
|
79
|
+
|
71
80
|
Scenario: Missing directory
|
72
81
|
Given a successful login
|
73
82
|
When the client successfully lists the directory "missing/file"
|
@@ -41,6 +41,15 @@ Feature: Name List
|
|
41
41
|
Then the file list should be in short form
|
42
42
|
And the file list should contain "foo"
|
43
43
|
|
44
|
+
Scenario: '-a'
|
45
|
+
Given a successful login
|
46
|
+
And the server has file "foo"
|
47
|
+
And the server has file "bar"
|
48
|
+
When the client successfully name-lists the directory "-a"
|
49
|
+
Then the file list should be in short form
|
50
|
+
And the file list should contain "foo"
|
51
|
+
And the file list should contain "bar"
|
52
|
+
|
44
53
|
Scenario: Passive
|
45
54
|
Given a successful login
|
46
55
|
And the server has file "foo"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Feature: PASV
|
2
|
+
|
3
|
+
As a programmer
|
4
|
+
I want good error messages
|
5
|
+
So that I can correct problems
|
6
|
+
|
7
|
+
Background:
|
8
|
+
Given the test server is started
|
9
|
+
|
10
|
+
Scenario: No argument
|
11
|
+
Given a successful login
|
12
|
+
Then the client successfully sends "PASV"
|
13
|
+
|
14
|
+
Scenario: After "EPSV ALL"
|
15
|
+
Given a successful login
|
16
|
+
Given the client successfully sends "EPSV ALL"
|
17
|
+
When the client sends "PASV"
|
18
|
+
Then the server sends a not allowed after epsv all error
|
19
|
+
|
20
|
+
Scenario: Not logged in
|
21
|
+
Given a successful connection
|
22
|
+
When the client sends "EPSV"
|
23
|
+
Then the server returns a not logged in error
|
@@ -1,4 +1,4 @@
|
|
1
|
-
Feature:
|
1
|
+
Feature: PORT
|
2
2
|
|
3
3
|
As a programmer
|
4
4
|
I want good error messages
|
@@ -41,3 +41,9 @@ Feature: Port
|
|
41
41
|
Given a successful login
|
42
42
|
When the client sends PORT "1,2,3,4,5,256"
|
43
43
|
Then the server returns a syntax error
|
44
|
+
|
45
|
+
Scenario: After "EPSV ALL"
|
46
|
+
Given a successful login
|
47
|
+
Given the client successfully sends "EPSV ALL"
|
48
|
+
When the client sends "PORT 1,2,3,4,4,0"
|
49
|
+
Then the server sends a not allowed after epsv all error
|
@@ -10,6 +10,10 @@ Given /^the test server has TLS mode "(\w+)"$/ do |mode|
|
|
10
10
|
server.tls = mode.to_sym
|
11
11
|
end
|
12
12
|
|
13
|
+
Given(/^the test server is bound to "(.*?)"$/) do |ip_address|
|
14
|
+
server.interface = ip_address
|
15
|
+
end
|
16
|
+
|
13
17
|
Given /^the test server has logging (enabled|disabled)$/ do |state|
|
14
18
|
server.logging = state == 'enabled'
|
15
19
|
end
|
@@ -3,7 +3,7 @@ require 'net/ftp'
|
|
3
3
|
|
4
4
|
When /^the( \w+)? client connects(?: with (\w+) TLS)?$/ do
|
5
5
|
|client_name, tls_mode|
|
6
|
-
tls_mode ||=
|
6
|
+
tls_mode ||= 'off'
|
7
7
|
client(client_name).tls_mode = tls_mode.to_sym
|
8
8
|
client(client_name).start
|
9
9
|
client(client_name).connect(server.host, server.port)
|
@@ -105,3 +105,11 @@ end
|
|
105
105
|
Then /^the server returns an already exists error$/ do
|
106
106
|
step 'the server returns a "550 Already exists" error'
|
107
107
|
end
|
108
|
+
|
109
|
+
Then /^the server returns a network protocol not supported error$/ do
|
110
|
+
step 'the server returns a "522 Network protocol" error'
|
111
|
+
end
|
112
|
+
|
113
|
+
Then /^the server sends a not allowed after epsv all error$/ do
|
114
|
+
step 'the server returns a "501 Not allowed after EPSV ALL" error'
|
115
|
+
end
|
@@ -219,6 +219,7 @@ class TestServer
|
|
219
219
|
def_delegator :@server, :'auth_level'
|
220
220
|
def_delegator :@server, :'auth_level='
|
221
221
|
def_delegator :@server, :'failed_login_delay='
|
222
|
+
def_delegator :@server, :'interface='
|
222
223
|
def_delegator :@server, :'max_connections='
|
223
224
|
def_delegator :@server, :'max_connections_per_ip='
|
224
225
|
def_delegator :@server, :'max_failed_logins='
|
@@ -250,7 +251,7 @@ class TestServer
|
|
250
251
|
end
|
251
252
|
|
252
253
|
def host
|
253
|
-
|
254
|
+
@server.interface
|
254
255
|
end
|
255
256
|
|
256
257
|
def user
|
data/ftpd.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "ftpd"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.9.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Wayne Conrad"]
|
12
|
-
s.date = "2013-
|
12
|
+
s.date = "2013-10-01"
|
13
13
|
s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and is unconditionally compliant per [RFC-1123][1]. It an be used as part of a test fixture or embedded in a program."
|
14
14
|
s.email = "wconrad@yagni.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -29,6 +29,7 @@ Gem::Specification.new do |s|
|
|
29
29
|
"doc/references.md",
|
30
30
|
"doc/rfc-compliance.md",
|
31
31
|
"examples/example.rb",
|
32
|
+
"examples/example_spec.rb",
|
32
33
|
"examples/hello_world.rb",
|
33
34
|
"features/example/eplf.feature",
|
34
35
|
"features/example/example.feature",
|
@@ -44,9 +45,12 @@ Gem::Specification.new do |s|
|
|
44
45
|
"features/ftp_server/delete.feature",
|
45
46
|
"features/ftp_server/directory_navigation.feature",
|
46
47
|
"features/ftp_server/disconnect_after_failed_logins.feature",
|
48
|
+
"features/ftp_server/eprt.feature",
|
49
|
+
"features/ftp_server/epsv.feature",
|
47
50
|
"features/ftp_server/features.feature",
|
48
51
|
"features/ftp_server/file_structure.feature",
|
49
52
|
"features/ftp_server/get.feature",
|
53
|
+
"features/ftp_server/get_ipv6.feature",
|
50
54
|
"features/ftp_server/get_tls.feature",
|
51
55
|
"features/ftp_server/help.feature",
|
52
56
|
"features/ftp_server/implicit_tls.feature",
|
@@ -64,6 +68,7 @@ Gem::Specification.new do |s|
|
|
64
68
|
"features/ftp_server/name_list_tls.feature",
|
65
69
|
"features/ftp_server/noop.feature",
|
66
70
|
"features/ftp_server/options.feature",
|
71
|
+
"features/ftp_server/pasv.feature",
|
67
72
|
"features/ftp_server/port.feature",
|
68
73
|
"features/ftp_server/put.feature",
|
69
74
|
"features/ftp_server/put_tls.feature",
|
@@ -144,7 +149,9 @@ Gem::Specification.new do |s|
|
|
144
149
|
"lib/ftpd/insecure_certificate.rb",
|
145
150
|
"lib/ftpd/list_format/eplf.rb",
|
146
151
|
"lib/ftpd/list_format/ls.rb",
|
152
|
+
"lib/ftpd/list_path.rb",
|
147
153
|
"lib/ftpd/null_logger.rb",
|
154
|
+
"lib/ftpd/protocols.rb",
|
148
155
|
"lib/ftpd/read_only_disk_file_system.rb",
|
149
156
|
"lib/ftpd/server.rb",
|
150
157
|
"lib/ftpd/session.rb",
|
@@ -167,7 +174,9 @@ Gem::Specification.new do |s|
|
|
167
174
|
"spec/file_system_error_translator_spec.rb",
|
168
175
|
"spec/list_format/eplf_spec.rb",
|
169
176
|
"spec/list_format/ls_spec.rb",
|
177
|
+
"spec/list_path_spec.rb",
|
170
178
|
"spec/null_logger_spec.rb",
|
179
|
+
"spec/protocols_spec.rb",
|
171
180
|
"spec/spec_helper.rb",
|
172
181
|
"spec/telnet_spec.rb",
|
173
182
|
"spec/translate_exceptions_spec.rb"
|
@@ -175,17 +184,17 @@ Gem::Specification.new do |s|
|
|
175
184
|
s.homepage = "http://github.com/wconrad/ftpd"
|
176
185
|
s.licenses = ["MIT"]
|
177
186
|
s.require_paths = ["lib"]
|
178
|
-
s.rubygems_version = "
|
187
|
+
s.rubygems_version = "2.0.0"
|
179
188
|
s.summary = "Pure Ruby FTP server library"
|
180
189
|
|
181
190
|
if s.respond_to? :specification_version then
|
182
|
-
s.specification_version =
|
191
|
+
s.specification_version = 4
|
183
192
|
|
184
193
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
185
194
|
s.add_runtime_dependency(%q<memoizer>, ["~> 1.0.1"])
|
186
195
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
187
196
|
s.add_development_dependency(%q<double-bag-ftps>, [">= 0"])
|
188
|
-
s.add_development_dependency(%q<jeweler>, ["
|
197
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
189
198
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
190
199
|
s.add_development_dependency(%q<redcarpet>, [">= 0"])
|
191
200
|
s.add_development_dependency(%q<rspec>, [">= 0"])
|
@@ -195,7 +204,7 @@ Gem::Specification.new do |s|
|
|
195
204
|
s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
|
196
205
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
197
206
|
s.add_dependency(%q<double-bag-ftps>, [">= 0"])
|
198
|
-
s.add_dependency(%q<jeweler>, ["
|
207
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
199
208
|
s.add_dependency(%q<rake>, [">= 0"])
|
200
209
|
s.add_dependency(%q<redcarpet>, [">= 0"])
|
201
210
|
s.add_dependency(%q<rspec>, [">= 0"])
|
@@ -206,7 +215,7 @@ Gem::Specification.new do |s|
|
|
206
215
|
s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
|
207
216
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
208
217
|
s.add_dependency(%q<double-bag-ftps>, [">= 0"])
|
209
|
-
s.add_dependency(%q<jeweler>, ["
|
218
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
210
219
|
s.add_dependency(%q<rake>, [">= 0"])
|
211
220
|
s.add_dependency(%q<redcarpet>, [">= 0"])
|
212
221
|
s.add_dependency(%q<rspec>, [">= 0"])
|
data/lib/ftpd.rb
CHANGED
@@ -25,7 +25,9 @@ module Ftpd
|
|
25
25
|
autoload :FileSystemMethodMissing, 'ftpd/file_system_method_missing'
|
26
26
|
autoload :FtpServer, 'ftpd/ftp_server'
|
27
27
|
autoload :InsecureCertificate, 'ftpd/insecure_certificate'
|
28
|
+
autoload :ListPath, 'ftpd/list_path'
|
28
29
|
autoload :NullLogger, 'ftpd/null_logger'
|
30
|
+
autoload :Protocols, 'ftpd/protocols'
|
29
31
|
autoload :ReadOnlyDiskFileSystem, 'ftpd/read_only_disk_file_system'
|
30
32
|
autoload :Server, 'ftpd/server'
|
31
33
|
autoload :Session, 'ftpd/session'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Ftpd
|
2
|
+
|
3
|
+
# Functions for manipulating LIST and NLST arguments
|
4
|
+
|
5
|
+
module ListPath
|
6
|
+
|
7
|
+
# Turn the argument to LIST/NLST into a path
|
8
|
+
#
|
9
|
+
# @param argument [String] The argument, or nil if not present
|
10
|
+
# @return [String] The path
|
11
|
+
#
|
12
|
+
# Although compliant with the spec, this function does not do
|
13
|
+
# these things that traditional Unix FTP servers do:
|
14
|
+
#
|
15
|
+
# * Allow multiple paths
|
16
|
+
# * Handle switches such as "-a"
|
17
|
+
#
|
18
|
+
# See: http://cr.yp.to/ftp/list.html sections "LIST parameters"
|
19
|
+
# and "LIST wildcards"
|
20
|
+
|
21
|
+
def list_path(argument)
|
22
|
+
argument ||= '.'
|
23
|
+
argument = '' if argument =~ /^-/
|
24
|
+
argument
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Ftpd
|
2
|
+
|
3
|
+
# With the commands EPORT and EPSV, the client sends a protocol code
|
4
|
+
# to indicate whether it wants an IPV4 or an IPV6 connection. This
|
5
|
+
# class contains functions related to that protocol code.
|
6
|
+
|
7
|
+
class Protocols
|
8
|
+
|
9
|
+
module Codes
|
10
|
+
IPV4 = 1
|
11
|
+
IPV6 = 2
|
12
|
+
end
|
13
|
+
include Codes
|
14
|
+
|
15
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] The socket.
|
16
|
+
# It doesn't matter whether it's the server socket (the one on
|
17
|
+
# which #accept is called), or the socket returned by #accept.
|
18
|
+
|
19
|
+
def initialize(socket)
|
20
|
+
@socket = socket
|
21
|
+
end
|
22
|
+
|
23
|
+
# Can the socket support a connection in the indicated protocol?
|
24
|
+
#
|
25
|
+
# @param protocol_code [Integer] protocol code
|
26
|
+
|
27
|
+
def supports_protocol?(protocol_code)
|
28
|
+
protocol_codes.include?(protocol_code)
|
29
|
+
end
|
30
|
+
|
31
|
+
# What protocol codes does the socket support?
|
32
|
+
#
|
33
|
+
# @return [Array<Integer>] List of protocol codes
|
34
|
+
|
35
|
+
def protocol_codes
|
36
|
+
[
|
37
|
+
(IPV4 if supports_ipv4?),
|
38
|
+
(IPV6 if supports_ipv6?),
|
39
|
+
].compact
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def supports_ipv4?
|
45
|
+
@socket.local_address.ipv4? || ipv6_dual_stack?
|
46
|
+
end
|
47
|
+
|
48
|
+
def supports_ipv6?
|
49
|
+
@socket.local_address.ipv6?
|
50
|
+
end
|
51
|
+
|
52
|
+
def ipv6_dual_stack?
|
53
|
+
v6only = @socket.getsockopt(Socket::IPPROTO_IPV6,
|
54
|
+
Socket::IPV6_V6ONLY).unpack('i')
|
55
|
+
v6only == [0]
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/ftpd/session.rb
CHANGED
@@ -4,6 +4,7 @@ module Ftpd
|
|
4
4
|
class Session
|
5
5
|
|
6
6
|
include Error
|
7
|
+
include ListPath
|
7
8
|
|
8
9
|
def initialize(opts)
|
9
10
|
@failed_login_delay = opts[:failed_login_delay]
|
@@ -25,6 +26,7 @@ module Ftpd
|
|
25
26
|
end
|
26
27
|
@command_sequence_checker = init_command_sequence_checker
|
27
28
|
set_socket_options
|
29
|
+
@protocols = Protocols.new(@socket)
|
28
30
|
initialize_session
|
29
31
|
end
|
30
32
|
|
@@ -108,6 +110,7 @@ module Ftpd
|
|
108
110
|
|
109
111
|
def cmd_port(argument)
|
110
112
|
ensure_logged_in
|
113
|
+
ensure_not_epsv_all
|
111
114
|
pieces = argument.split(/,/)
|
112
115
|
syntax_error unless pieces.size == 6
|
113
116
|
pieces.collect! do |s|
|
@@ -118,11 +121,7 @@ module Ftpd
|
|
118
121
|
end
|
119
122
|
hostname = pieces[0..3].join('.')
|
120
123
|
port = pieces[4] << 8 | pieces[5]
|
121
|
-
|
122
|
-
error "504 Command not implemented for that parameter"
|
123
|
-
end
|
124
|
-
@data_hostname = hostname
|
125
|
-
@data_port = port
|
124
|
+
set_active_mode_address hostname, port
|
126
125
|
reply "200 PORT command successful"
|
127
126
|
end
|
128
127
|
|
@@ -201,8 +200,7 @@ module Ftpd
|
|
201
200
|
ensure_logged_in
|
202
201
|
ensure_file_system_supports :dir
|
203
202
|
ensure_file_system_supports :file_info
|
204
|
-
path = argument
|
205
|
-
path ||= '.'
|
203
|
+
path = list_path(argument)
|
206
204
|
path = File.expand_path(path, @name_prefix)
|
207
205
|
transmit_file(list(path), 'A')
|
208
206
|
end
|
@@ -212,8 +210,7 @@ module Ftpd
|
|
212
210
|
close_data_server_socket_when_done do
|
213
211
|
ensure_logged_in
|
214
212
|
ensure_file_system_supports :dir
|
215
|
-
path = argument
|
216
|
-
path ||= '.'
|
213
|
+
path = list_path(argument)
|
217
214
|
path = File.expand_path(path, @name_prefix)
|
218
215
|
transmit_file(name_list(path), 'A')
|
219
216
|
end
|
@@ -265,6 +262,7 @@ module Ftpd
|
|
265
262
|
|
266
263
|
def cmd_pasv(argument)
|
267
264
|
ensure_logged_in
|
265
|
+
ensure_not_epsv_all
|
268
266
|
if @data_server
|
269
267
|
reply "200 Already in passive mode"
|
270
268
|
else
|
@@ -360,6 +358,12 @@ module Ftpd
|
|
360
358
|
end
|
361
359
|
end
|
362
360
|
|
361
|
+
def ensure_not_epsv_all
|
362
|
+
if @epsv_all
|
363
|
+
error "501 Not allowed after EPSV ALL"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
363
367
|
def tls_enabled?
|
364
368
|
@tls != :off
|
365
369
|
end
|
@@ -490,6 +494,49 @@ module Ftpd
|
|
490
494
|
error '501 Unsupported option'
|
491
495
|
end
|
492
496
|
|
497
|
+
def cmd_eprt(argument)
|
498
|
+
ensure_logged_in
|
499
|
+
ensure_not_epsv_all
|
500
|
+
delim = argument[0..0]
|
501
|
+
parts = argument.split(delim)[1..-1]
|
502
|
+
syntax_error unless parts.size == 3
|
503
|
+
protocol_code, address, port = *parts
|
504
|
+
protocol_code = protocol_code.to_i
|
505
|
+
ensure_protocol_supported protocol_code
|
506
|
+
port = port.to_i
|
507
|
+
set_active_mode_address address, port
|
508
|
+
reply "200 EPRT command successful"
|
509
|
+
end
|
510
|
+
|
511
|
+
def ensure_protocol_supported(protocol_code)
|
512
|
+
unless @protocols.supports_protocol?(protocol_code)
|
513
|
+
protocol_list = @protocols.protocol_codes.join(',')
|
514
|
+
error("522 Network protocol #{protocol_code} not supported, "\
|
515
|
+
"use (#{protocol_list})")
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
def cmd_epsv(argument)
|
520
|
+
ensure_logged_in
|
521
|
+
if @data_server
|
522
|
+
reply "200 Already in passive mode"
|
523
|
+
else
|
524
|
+
if argument == 'ALL'
|
525
|
+
@epsv_all = true
|
526
|
+
reply "220 EPSV now required for port setup"
|
527
|
+
else
|
528
|
+
protocol_code = argument && argument.to_i
|
529
|
+
if protocol_code
|
530
|
+
ensure_protocol_supported protocol_code
|
531
|
+
end
|
532
|
+
interface = @socket.addr[3]
|
533
|
+
@data_server = TCPServer.new(interface, 0)
|
534
|
+
port = @data_server.addr[1]
|
535
|
+
reply "229 Entering extended passive mode (|||#{port}|)"
|
536
|
+
end
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
493
540
|
unimplemented :abor
|
494
541
|
unimplemented :rein
|
495
542
|
unimplemented :rest
|
@@ -498,7 +545,8 @@ module Ftpd
|
|
498
545
|
|
499
546
|
def extensions
|
500
547
|
[
|
501
|
-
(TLS_EXTENSIONS if tls_enabled?)
|
548
|
+
(TLS_EXTENSIONS if tls_enabled?),
|
549
|
+
IPV6_EXTENSIONS,
|
502
550
|
].flatten.compact
|
503
551
|
end
|
504
552
|
|
@@ -508,6 +556,11 @@ module Ftpd
|
|
508
556
|
'PROT'
|
509
557
|
]
|
510
558
|
|
559
|
+
IPV6_EXTENSIONS = [
|
560
|
+
'EPRT',
|
561
|
+
'EPSV',
|
562
|
+
]
|
563
|
+
|
511
564
|
def supported_commands
|
512
565
|
private_methods.map do |method|
|
513
566
|
method.to_s[/^cmd_(\w+)$/, 1]
|
@@ -823,6 +876,18 @@ module Ftpd
|
|
823
876
|
end
|
824
877
|
end
|
825
878
|
|
879
|
+
def set_data_address(n)
|
880
|
+
|
881
|
+
end
|
882
|
+
|
883
|
+
def set_active_mode_address(address, port)
|
884
|
+
if port > 0xffff || port < 1024 && !@allow_low_data_ports
|
885
|
+
error "504 Command not implemented for that parameter"
|
886
|
+
end
|
887
|
+
@data_hostname = address
|
888
|
+
@data_port = port
|
889
|
+
end
|
890
|
+
|
826
891
|
def initialize_session
|
827
892
|
@logged_in = false
|
828
893
|
@data_type = 'A'
|
@@ -833,6 +898,7 @@ module Ftpd
|
|
833
898
|
@data_hostname = nil
|
834
899
|
@data_port = nil
|
835
900
|
@protection_buffer_size_set = 0
|
901
|
+
@epsv_all = false
|
836
902
|
close_data_server_socket
|
837
903
|
reset_failed_auths
|
838
904
|
end
|