ftpd 0.8.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|