ftpd 0.5.0 → 0.6.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/Changelog.md +25 -3
- data/Gemfile +0 -1
- data/Gemfile.lock +0 -5
- data/README.md +109 -51
- data/VERSION +1 -1
- data/doc/benchmarks.md +4 -3
- data/doc/references.md +1 -1
- data/doc/rfc-compliance.md +18 -18
- data/examples/example.rb +1 -0
- data/features/ftp_server/abort.feature +13 -0
- data/features/ftp_server/command_errors.feature +0 -12
- data/features/ftp_server/delay_after_failed_login.feature +23 -0
- data/features/ftp_server/disconnect_after_failed_logins.feature +25 -0
- data/features/ftp_server/features.feature +26 -0
- data/features/ftp_server/max_connections.feature +39 -0
- data/features/ftp_server/options.feature +17 -0
- data/features/ftp_server/put_tls.feature +1 -1
- data/features/ftp_server/reinitialize.feature +13 -0
- data/features/ftp_server/site.feature +13 -0
- data/features/ftp_server/status.feature +1 -2
- data/features/ftp_server/step_definitions/test_server.rb +20 -0
- data/features/ftp_server/structure_mount.feature +13 -0
- data/features/ftp_server/timeout.feature +3 -3
- data/features/step_definitions/append.rb +2 -2
- data/features/step_definitions/client.rb +19 -9
- data/features/step_definitions/client_and_server_files.rb +2 -2
- data/features/step_definitions/client_files.rb +4 -4
- data/features/step_definitions/command.rb +1 -1
- data/features/step_definitions/connect.rb +28 -5
- data/features/step_definitions/delete.rb +2 -2
- data/features/step_definitions/directory_navigation.rb +4 -4
- data/features/step_definitions/error_replies.rb +12 -0
- data/features/step_definitions/features.rb +21 -0
- data/features/step_definitions/file_structure.rb +2 -2
- data/features/step_definitions/generic_send.rb +1 -1
- data/features/step_definitions/get.rb +2 -2
- data/features/step_definitions/help.rb +1 -1
- data/features/step_definitions/invalid_commands.rb +2 -2
- data/features/step_definitions/list.rb +2 -2
- data/features/step_definitions/login.rb +3 -3
- data/features/step_definitions/mkdir.rb +1 -1
- data/features/step_definitions/mode.rb +2 -2
- data/features/step_definitions/options.rb +9 -0
- data/features/step_definitions/passive.rb +1 -1
- data/features/step_definitions/port.rb +1 -1
- data/features/step_definitions/put.rb +3 -3
- data/features/step_definitions/quit.rb +2 -2
- data/features/step_definitions/rename.rb +1 -1
- data/features/step_definitions/rmdir.rb +1 -1
- data/features/step_definitions/server_title.rb +12 -0
- data/features/step_definitions/status.rb +1 -9
- data/features/step_definitions/system.rb +1 -1
- data/features/step_definitions/timing.rb +19 -0
- data/features/step_definitions/type.rb +2 -2
- data/features/support/test_client.rb +62 -7
- data/features/support/test_server.rb +4 -0
- data/ftpd.gemspec +21 -9
- data/lib/ftpd.rb +4 -0
- data/lib/ftpd/command_sequence_checker.rb +4 -2
- data/lib/ftpd/config.rb +13 -0
- data/lib/ftpd/connection_throttle.rb +56 -0
- data/lib/ftpd/connection_tracker.rb +110 -0
- data/lib/ftpd/disk_file_system.rb +2 -2
- data/lib/ftpd/ftp_server.rb +118 -35
- data/lib/ftpd/server.rb +27 -3
- data/lib/ftpd/session.rb +84 -25
- data/lib/ftpd/tls_server.rb +11 -5
- data/rake_tasks/cucumber.rake +1 -0
- data/rake_tasks/jeweler.rake +1 -1
- data/spec/connection_throttle_spec.rb +96 -0
- data/spec/connection_tracker_spec.rb +126 -0
- data/spec/spec_helper.rb +1 -0
- metadata +22 -23
- data/config/cucumber.yml +0 -2
- data/features/step_definitions/stop_server.rb +0 -3
- data/features/step_definitions/timeout.rb +0 -11
@@ -218,6 +218,10 @@ class TestServer
|
|
218
218
|
def_delegator :@server, :'allow_low_data_ports='
|
219
219
|
def_delegator :@server, :'auth_level'
|
220
220
|
def_delegator :@server, :'auth_level='
|
221
|
+
def_delegator :@server, :'failed_login_delay='
|
222
|
+
def_delegator :@server, :'max_connections='
|
223
|
+
def_delegator :@server, :'max_connections_per_ip='
|
224
|
+
def_delegator :@server, :'max_failed_logins='
|
221
225
|
def_delegator :@server, :'server_name'
|
222
226
|
def_delegator :@server, :'server_name='
|
223
227
|
def_delegator :@server, :'session_timeout='
|
data/ftpd.gemspec
CHANGED
@@ -5,12 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "ftpd"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.6.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-03-
|
13
|
-
s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and is unconditionally
|
12
|
+
s.date = "2013-03-19"
|
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 = [
|
16
16
|
"LICENSE.md",
|
@@ -25,7 +25,6 @@ Gem::Specification.new do |s|
|
|
25
25
|
"README.md",
|
26
26
|
"Rakefile",
|
27
27
|
"VERSION",
|
28
|
-
"config/cucumber.yml",
|
29
28
|
"doc/benchmarks.md",
|
30
29
|
"doc/references.md",
|
31
30
|
"doc/rfc-compliance.md",
|
@@ -35,13 +34,17 @@ Gem::Specification.new do |s|
|
|
35
34
|
"features/example/example.feature",
|
36
35
|
"features/example/read_only.feature",
|
37
36
|
"features/example/step_definitions/example_server.rb",
|
37
|
+
"features/ftp_server/abort.feature",
|
38
38
|
"features/ftp_server/allo.feature",
|
39
39
|
"features/ftp_server/append.feature",
|
40
40
|
"features/ftp_server/cdup.feature",
|
41
41
|
"features/ftp_server/command_errors.feature",
|
42
42
|
"features/ftp_server/concurrent_sessions.feature",
|
43
|
+
"features/ftp_server/delay_after_failed_login.feature",
|
43
44
|
"features/ftp_server/delete.feature",
|
44
45
|
"features/ftp_server/directory_navigation.feature",
|
46
|
+
"features/ftp_server/disconnect_after_failed_logins.feature",
|
47
|
+
"features/ftp_server/features.feature",
|
45
48
|
"features/ftp_server/file_structure.feature",
|
46
49
|
"features/ftp_server/get.feature",
|
47
50
|
"features/ftp_server/get_tls.feature",
|
@@ -54,21 +57,26 @@ Gem::Specification.new do |s|
|
|
54
57
|
"features/ftp_server/login_auth_level_account.feature",
|
55
58
|
"features/ftp_server/login_auth_level_password.feature",
|
56
59
|
"features/ftp_server/login_auth_level_user.feature",
|
60
|
+
"features/ftp_server/max_connections.feature",
|
57
61
|
"features/ftp_server/mkdir.feature",
|
58
62
|
"features/ftp_server/mode.feature",
|
59
63
|
"features/ftp_server/name_list.feature",
|
60
64
|
"features/ftp_server/name_list_tls.feature",
|
61
65
|
"features/ftp_server/noop.feature",
|
66
|
+
"features/ftp_server/options.feature",
|
62
67
|
"features/ftp_server/port.feature",
|
63
68
|
"features/ftp_server/put.feature",
|
64
69
|
"features/ftp_server/put_tls.feature",
|
65
70
|
"features/ftp_server/put_unique.feature",
|
66
71
|
"features/ftp_server/quit.feature",
|
72
|
+
"features/ftp_server/reinitialize.feature",
|
67
73
|
"features/ftp_server/rename.feature",
|
68
74
|
"features/ftp_server/rmdir.feature",
|
75
|
+
"features/ftp_server/site.feature",
|
69
76
|
"features/ftp_server/status.feature",
|
70
77
|
"features/ftp_server/step_definitions/logging.rb",
|
71
78
|
"features/ftp_server/step_definitions/test_server.rb",
|
79
|
+
"features/ftp_server/structure_mount.feature",
|
72
80
|
"features/ftp_server/syntax_errors.feature",
|
73
81
|
"features/ftp_server/syst.feature",
|
74
82
|
"features/ftp_server/timeout.feature",
|
@@ -82,6 +90,7 @@ Gem::Specification.new do |s|
|
|
82
90
|
"features/step_definitions/delete.rb",
|
83
91
|
"features/step_definitions/directory_navigation.rb",
|
84
92
|
"features/step_definitions/error_replies.rb",
|
93
|
+
"features/step_definitions/features.rb",
|
85
94
|
"features/step_definitions/file_structure.rb",
|
86
95
|
"features/step_definitions/generic_send.rb",
|
87
96
|
"features/step_definitions/get.rb",
|
@@ -93,6 +102,7 @@ Gem::Specification.new do |s|
|
|
93
102
|
"features/step_definitions/mkdir.rb",
|
94
103
|
"features/step_definitions/mode.rb",
|
95
104
|
"features/step_definitions/noop.rb",
|
105
|
+
"features/step_definitions/options.rb",
|
96
106
|
"features/step_definitions/passive.rb",
|
97
107
|
"features/step_definitions/pending.rb",
|
98
108
|
"features/step_definitions/port.rb",
|
@@ -101,11 +111,11 @@ Gem::Specification.new do |s|
|
|
101
111
|
"features/step_definitions/rename.rb",
|
102
112
|
"features/step_definitions/rmdir.rb",
|
103
113
|
"features/step_definitions/server_files.rb",
|
114
|
+
"features/step_definitions/server_title.rb",
|
104
115
|
"features/step_definitions/status.rb",
|
105
|
-
"features/step_definitions/stop_server.rb",
|
106
116
|
"features/step_definitions/success_replies.rb",
|
107
117
|
"features/step_definitions/system.rb",
|
108
|
-
"features/step_definitions/
|
118
|
+
"features/step_definitions/timing.rb",
|
109
119
|
"features/step_definitions/type.rb",
|
110
120
|
"features/support/env.rb",
|
111
121
|
"features/support/example_server.rb",
|
@@ -121,6 +131,9 @@ Gem::Specification.new do |s|
|
|
121
131
|
"lib/ftpd.rb",
|
122
132
|
"lib/ftpd/auth_levels.rb",
|
123
133
|
"lib/ftpd/command_sequence_checker.rb",
|
134
|
+
"lib/ftpd/config.rb",
|
135
|
+
"lib/ftpd/connection_throttle.rb",
|
136
|
+
"lib/ftpd/connection_tracker.rb",
|
124
137
|
"lib/ftpd/disk_file_system.rb",
|
125
138
|
"lib/ftpd/error.rb",
|
126
139
|
"lib/ftpd/exception_translator.rb",
|
@@ -146,6 +159,8 @@ Gem::Specification.new do |s|
|
|
146
159
|
"rake_tasks/test.rake",
|
147
160
|
"rake_tasks/yard.rake",
|
148
161
|
"spec/command_sequence_checker_spec.rb",
|
162
|
+
"spec/connection_throttle_spec.rb",
|
163
|
+
"spec/connection_tracker_spec.rb",
|
149
164
|
"spec/disk_file_system_spec.rb",
|
150
165
|
"spec/exception_translator_spec.rb",
|
151
166
|
"spec/file_info_spec.rb",
|
@@ -170,7 +185,6 @@ Gem::Specification.new do |s|
|
|
170
185
|
s.add_runtime_dependency(%q<memoizer>, ["~> 1.0.1"])
|
171
186
|
s.add_development_dependency(%q<cucumber>, [">= 0"])
|
172
187
|
s.add_development_dependency(%q<double-bag-ftps>, [">= 0"])
|
173
|
-
s.add_development_dependency(%q<fuubar-cucumber>, [">= 0"])
|
174
188
|
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
175
189
|
s.add_development_dependency(%q<rake>, [">= 0"])
|
176
190
|
s.add_development_dependency(%q<redcarpet>, [">= 0"])
|
@@ -181,7 +195,6 @@ Gem::Specification.new do |s|
|
|
181
195
|
s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
|
182
196
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
183
197
|
s.add_dependency(%q<double-bag-ftps>, [">= 0"])
|
184
|
-
s.add_dependency(%q<fuubar-cucumber>, [">= 0"])
|
185
198
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
186
199
|
s.add_dependency(%q<rake>, [">= 0"])
|
187
200
|
s.add_dependency(%q<redcarpet>, [">= 0"])
|
@@ -193,7 +206,6 @@ Gem::Specification.new do |s|
|
|
193
206
|
s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
|
194
207
|
s.add_dependency(%q<cucumber>, [">= 0"])
|
195
208
|
s.add_dependency(%q<double-bag-ftps>, [">= 0"])
|
196
|
-
s.add_dependency(%q<fuubar-cucumber>, [">= 0"])
|
197
209
|
s.add_dependency(%q<jeweler>, [">= 0"])
|
198
210
|
s.add_dependency(%q<rake>, [">= 0"])
|
199
211
|
s.add_dependency(%q<redcarpet>, [">= 0"])
|
data/lib/ftpd.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'fileutils'
|
2
|
+
require 'forwardable'
|
2
3
|
require 'logger'
|
3
4
|
require 'memoizer'
|
4
5
|
require 'openssl'
|
5
6
|
require 'pathname'
|
6
7
|
require 'shellwords'
|
7
8
|
require 'socket'
|
9
|
+
require 'thread'
|
8
10
|
require 'tmpdir'
|
9
11
|
|
10
12
|
module Ftpd
|
@@ -13,6 +15,8 @@ module Ftpd
|
|
13
15
|
autoload :Ls, 'ftpd/list_format/ls'
|
14
16
|
end
|
15
17
|
autoload :CommandSequenceChecker, 'ftpd/command_sequence_checker'
|
18
|
+
autoload :ConnectionThrottle, 'ftpd/connection_throttle'
|
19
|
+
autoload :ConnectionTracker, 'ftpd/connection_tracker'
|
16
20
|
autoload :DiskFileSystem, 'ftpd/disk_file_system'
|
17
21
|
autoload :Error, 'ftpd/error'
|
18
22
|
autoload :ExceptionTranslator, 'ftpd/exception_translator'
|
@@ -11,11 +11,13 @@ module Ftpd
|
|
11
11
|
|
12
12
|
def initialize
|
13
13
|
@must_expect = []
|
14
|
+
@expected_command = nil
|
14
15
|
end
|
15
16
|
|
16
17
|
# Set the command to expect next. If not set, then any command
|
17
18
|
# will be accepted, so long as it hasn't been registered using
|
18
|
-
# {#must_expect}.
|
19
|
+
# {#must_expect}. Otherwise, the set command must be next or a
|
20
|
+
# sequence error will result.
|
19
21
|
#
|
20
22
|
# @param command [String] The command. Must be lowercase.
|
21
23
|
|
@@ -34,7 +36,7 @@ module Ftpd
|
|
34
36
|
# Check a command. If expecting a specific command and this
|
35
37
|
# command isn't it, then raise an error that will cause a "503 Bad
|
36
38
|
# sequence" error to be sent. After checking, the expected
|
37
|
-
# command is cleared and any command will be accepted
|
39
|
+
# command is cleared and any command will be accepted until
|
38
40
|
# {#expect} is called again.
|
39
41
|
#
|
40
42
|
# @param command [String] The command. Must be lowercase.
|
data/lib/ftpd/config.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
module Ftpd
|
2
|
+
class Config
|
3
|
+
|
4
|
+
# The number of seconds to delay before replying. This is for
|
5
|
+
# testing client timeouts.
|
6
|
+
# Defaults to 0 (no delay).
|
7
|
+
#
|
8
|
+
# Change to this attribute only take effect for new sessions.
|
9
|
+
|
10
|
+
attr_accessor :response_delay
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Ftpd
|
2
|
+
|
3
|
+
# This class limits the number of connections
|
4
|
+
|
5
|
+
class ConnectionThrottle
|
6
|
+
|
7
|
+
DEFAULT_MAX_CONNECTIONS = nil
|
8
|
+
DEFAULT_MAX_CONNECTIONS_PER_IP = nil
|
9
|
+
|
10
|
+
# The maximum number of connections, or nil if there is no limit.
|
11
|
+
# @return [Integer]
|
12
|
+
|
13
|
+
attr_accessor :max_connections
|
14
|
+
|
15
|
+
# The maximum number of connections for an IP, or nil if there is
|
16
|
+
# no limit.
|
17
|
+
# @return [Integer]
|
18
|
+
|
19
|
+
attr_accessor :max_connections_per_ip
|
20
|
+
|
21
|
+
# @param connection_tracker [ConnectionTracker]
|
22
|
+
|
23
|
+
def initialize(connection_tracker)
|
24
|
+
@max_connections = DEFAULT_MAX_CONNECTIONS
|
25
|
+
@max_connections_per_ip = DEFAULT_MAX_CONNECTIONS_PER_IP
|
26
|
+
@connection_tracker = connection_tracker
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Boolean] true if the connection should be allowed
|
30
|
+
|
31
|
+
def allow?(socket)
|
32
|
+
allow_by_total_count &&
|
33
|
+
allow_by_ip_count(socket)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Reject a connection
|
37
|
+
|
38
|
+
def deny(socket)
|
39
|
+
socket.write "421 Too many connections\r\n"
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def allow_by_total_count
|
45
|
+
return true unless @max_connections
|
46
|
+
@connection_tracker.connections < @max_connections
|
47
|
+
end
|
48
|
+
|
49
|
+
def allow_by_ip_count(socket)
|
50
|
+
return true unless @max_connections_per_ip
|
51
|
+
@connection_tracker.connections_for(socket) < @max_connections_per_ip
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Ftpd
|
2
|
+
|
3
|
+
# This class keeps track of connections
|
4
|
+
|
5
|
+
class ConnectionTracker
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@mutex = Mutex.new
|
9
|
+
@connections = {}
|
10
|
+
@socket_ips ={}
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return the total number of connections
|
14
|
+
|
15
|
+
def connections
|
16
|
+
@mutex.synchronize do
|
17
|
+
@connections.values.inject(0, &:+)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# Return the number of connections for a socket
|
22
|
+
|
23
|
+
def connections_for(socket)
|
24
|
+
@mutex.synchronize do
|
25
|
+
ip = peer_ip(socket)
|
26
|
+
@connections[ip] || 0
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Track a connection. Yields to a block; the connection is
|
31
|
+
# tracked until the block returns.
|
32
|
+
|
33
|
+
def track(socket)
|
34
|
+
start_track socket
|
35
|
+
begin
|
36
|
+
yield
|
37
|
+
ensure
|
38
|
+
stop_track socket
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return the number of known IPs. This exists for the benefit of
|
43
|
+
# the test, so that it can know the tracker has properly forgotten
|
44
|
+
# about an IP with no connections.
|
45
|
+
|
46
|
+
def known_ip_count
|
47
|
+
@mutex.synchronize do
|
48
|
+
@connections.size
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Start tracking a connection
|
55
|
+
|
56
|
+
def start_track(socket)
|
57
|
+
@mutex.synchronize do
|
58
|
+
ip = peer_ip(socket)
|
59
|
+
@connections[ip] ||= 0
|
60
|
+
@connections[ip] += 1
|
61
|
+
@socket_ips[socket.object_id] = ip
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Stop tracking a connection
|
66
|
+
|
67
|
+
def stop_track(socket)
|
68
|
+
@mutex.synchronize do
|
69
|
+
ip = @socket_ips.delete(socket.object_id)
|
70
|
+
if (@connections[ip] -= 1) == 0
|
71
|
+
@connections.delete(ip)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Obtain the IP that the client connected _from_.
|
77
|
+
#
|
78
|
+
# How this is done depends upon which type of socket (SSL or not)
|
79
|
+
# and what version of Ruby.
|
80
|
+
#
|
81
|
+
# * SSL socket
|
82
|
+
# * #peeraddr. Uses BasicSocket.do_not_reverse_lookup.
|
83
|
+
# * Ruby 1.8.7
|
84
|
+
# * #peeraddr, which does not take the "reverse lookup"
|
85
|
+
# argument, relying instead using
|
86
|
+
# BasicSocket.do_not_reverse_lookup.
|
87
|
+
# * #getpeername, which does not do a reverse lookup. It is a
|
88
|
+
# little uglier than #peeraddr.
|
89
|
+
# * Ruby >=1.9.3
|
90
|
+
# * #peeraddr, which takes the "reverse lookup" argument.
|
91
|
+
# * #getpeername - same as 1.8.7
|
92
|
+
|
93
|
+
# @return [String] IP address
|
94
|
+
|
95
|
+
def peer_ip(socket)
|
96
|
+
if socket.respond_to?(:getpeername)
|
97
|
+
# Non SSL
|
98
|
+
sockaddr = socket.getpeername
|
99
|
+
port, host = Socket.unpack_sockaddr_in(sockaddr)
|
100
|
+
host
|
101
|
+
else
|
102
|
+
# SSL
|
103
|
+
BasicSocket.do_not_reverse_lookup = true
|
104
|
+
socket.peeraddr.last
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
@@ -125,7 +125,7 @@ module Ftpd
|
|
125
125
|
|
126
126
|
# Write a file to disk.
|
127
127
|
# @param ftp_path [String] The virtual path
|
128
|
-
# @contents [String] The file's contents
|
128
|
+
# @param contents [String] The file's contents
|
129
129
|
#
|
130
130
|
# Called for:
|
131
131
|
# * STOR
|
@@ -153,7 +153,7 @@ module Ftpd
|
|
153
153
|
|
154
154
|
# Append to a file. If the file does not exist, create it.
|
155
155
|
# @param ftp_path [String] The virtual path
|
156
|
-
# @contents [String] The file's contents
|
156
|
+
# @param contents [String] The file's contents
|
157
157
|
#
|
158
158
|
# Called for:
|
159
159
|
# * APPE
|
data/lib/ftpd/ftp_server.rb
CHANGED
@@ -3,61 +3,125 @@
|
|
3
3
|
module Ftpd
|
4
4
|
class FtpServer < TlsServer
|
5
5
|
|
6
|
+
extend Forwardable
|
7
|
+
|
6
8
|
DEFAULT_SERVER_NAME = 'wconrad/ftpd'
|
7
9
|
DEFAULT_SESSION_TIMEOUT = 300 # seconds
|
8
10
|
|
9
|
-
#
|
10
|
-
#
|
11
|
-
#
|
11
|
+
# If true, allow the PORT command to specify privileged data ports
|
12
|
+
# (those below 1024). Defaults to false. Setting this to true
|
13
|
+
# makes it easier for an attacker to use the server to attack
|
14
|
+
# another server. See RFC 2577 section 3.
|
12
15
|
#
|
13
|
-
#
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# The class for formatting for LIST output. Defaults to
|
18
|
-
# {Ftpd::ListFormat::Ls}. Changes to this attribute only take
|
19
|
-
# effect for new sessions.
|
16
|
+
# Set this before calling #start.
|
17
|
+
#
|
18
|
+
# @return [Boolean]
|
20
19
|
|
21
|
-
attr_accessor :
|
20
|
+
attr_accessor :allow_low_data_ports
|
22
21
|
|
23
|
-
#
|
24
|
-
#
|
22
|
+
# The authentication level. One of:
|
23
|
+
#
|
25
24
|
# * Ftpd::AUTH_USER
|
26
25
|
# * Ftpd::AUTH_PASSWORD (default)
|
27
26
|
# * Ftpd::AUTH_ACCOUNT
|
27
|
+
#
|
28
|
+
# @return [Integer] The authentication level
|
28
29
|
|
29
30
|
attr_accessor :auth_level
|
30
31
|
|
31
|
-
# The
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
32
|
+
# The delay (in seconds) after a failed login. Defaults to 0.
|
33
|
+
# Setting this makes brute force password guessing less efficient
|
34
|
+
# for the attacker. RFC-2477 suggests a delay of 5 seconds.
|
35
|
+
|
36
|
+
attr_accessor :failed_login_delay
|
37
|
+
|
38
|
+
# The class for formatting for LIST output. Defaults to
|
39
|
+
# {Ftpd::ListFormat::Ls} (unix "ls -l" style).
|
40
|
+
#
|
41
|
+
# Set this before calling #start.
|
42
|
+
# @return [class that quacks like Ftpd::ListFormat::Ls]
|
43
|
+
|
44
|
+
attr_accessor :list_formatter
|
45
|
+
|
46
|
+
# The logger. Defaults to nil (no logging).
|
47
|
+
#
|
48
|
+
# Set this before calling #start.
|
49
|
+
#
|
50
|
+
# @return [Logger]
|
51
|
+
|
52
|
+
attr_accessor :log
|
53
|
+
|
54
|
+
# The maximum number of connections the server will allow.
|
55
|
+
# Defaults to {ConnectionThrottle::DEFAULT_MAX_CONNECTIONS}.
|
56
|
+
#
|
57
|
+
# Set this before calling #start.
|
58
|
+
#
|
59
|
+
# @!attribute max_connections
|
60
|
+
# @return [Integer]
|
61
|
+
|
62
|
+
def_delegator :@connection_throttle, :'max_connections'
|
63
|
+
def_delegator :@connection_throttle, :'max_connections='
|
64
|
+
|
65
|
+
# The maximum number of connections the server will allow from a
|
66
|
+
# given IP. Defaults to
|
67
|
+
# {ConnectionThrottle::DEFAULT_MAX_CONNECTIONS_PER_IP}.
|
68
|
+
#
|
69
|
+
# Set this before calling #start.
|
70
|
+
#
|
71
|
+
# @!attribute max_connections_per_ip
|
72
|
+
# @return [Integer]
|
73
|
+
|
74
|
+
def_delegator :@connection_throttle, :'max_connections_per_ip'
|
75
|
+
def_delegator :@connection_throttle, :'max_connections_per_ip='
|
76
|
+
|
77
|
+
# The maximum number of failed login attempts before disconnecting
|
78
|
+
# the user. Defaults to nil (no maximum). When set, this may
|
79
|
+
# makes brute-force password guessing attack less efficient.
|
80
|
+
#
|
81
|
+
# Set this before calling #start.
|
82
|
+
#
|
83
|
+
# @return [Integer]
|
84
|
+
|
85
|
+
attr_accessor :max_failed_logins
|
86
|
+
|
87
|
+
# The number of seconds to delay before replying. This is for
|
88
|
+
# testing, when you need to test, for example, client timeouts.
|
89
|
+
# Defaults to 0 (no delay).
|
90
|
+
#
|
91
|
+
# Set this before calling #start.
|
92
|
+
#
|
35
93
|
# @return [Numeric]
|
36
94
|
|
37
|
-
attr_accessor :
|
95
|
+
attr_accessor :response_delay
|
38
96
|
|
39
97
|
# The server's name, sent in a STAT reply. Defaults to
|
40
98
|
# {DEFAULT_SERVER_NAME}.
|
99
|
+
#
|
100
|
+
# Set this before calling #start.
|
101
|
+
#
|
102
|
+
# @return [String]
|
41
103
|
|
42
104
|
attr_accessor :server_name
|
43
105
|
|
44
106
|
# The server's version, sent in a STAT reply. Defaults to the
|
45
107
|
# contents of the VERSION file.
|
108
|
+
#
|
109
|
+
# Set this before calling #start.
|
110
|
+
#
|
111
|
+
# @return [String]
|
46
112
|
|
47
113
|
attr_accessor :server_version
|
48
114
|
|
49
|
-
# The
|
50
|
-
#
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
57
|
-
# section 3.
|
58
|
-
# @return [Boolean]
|
115
|
+
# The session timeout. When a session is awaiting a command, if
|
116
|
+
# one is not received in this many seconds, the session is
|
117
|
+
# disconnected. Defaults to {DEFAULT_SESSION_TIMEOUT}. If nil,
|
118
|
+
# then timeout is disabled.
|
119
|
+
#
|
120
|
+
# Set this before calling #start.
|
121
|
+
#
|
122
|
+
# @return [Numeric]
|
59
123
|
|
60
|
-
attr_accessor :
|
124
|
+
attr_accessor :session_timeout
|
61
125
|
|
62
126
|
# Create a new FTP server. The server won't start until the
|
63
127
|
# #start method is called.
|
@@ -79,23 +143,42 @@ module Ftpd
|
|
79
143
|
@server_name = DEFAULT_SERVER_NAME
|
80
144
|
@server_version = read_version_file
|
81
145
|
@allow_low_data_ports = false
|
146
|
+
@failed_login_delay = 0
|
82
147
|
@log = nil
|
148
|
+
@connection_tracker = ConnectionTracker.new
|
149
|
+
@connection_throttle = ConnectionThrottle.new(@connection_tracker)
|
83
150
|
end
|
84
151
|
|
85
152
|
private
|
86
153
|
|
154
|
+
def allow_session?(socket)
|
155
|
+
@connection_throttle.allow?(socket)
|
156
|
+
end
|
157
|
+
|
158
|
+
def deny_session socket
|
159
|
+
@connection_throttle.deny socket
|
160
|
+
end
|
161
|
+
|
87
162
|
def session(socket)
|
88
|
-
|
163
|
+
@connection_tracker.track(socket) do
|
164
|
+
run_session socket
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def run_session(socket)
|
169
|
+
Session.new(:allow_low_data_ports => allow_low_data_ports,
|
170
|
+
:auth_level => @auth_level,
|
89
171
|
:driver => @driver,
|
172
|
+
:failed_login_delay => @failed_login_delay,
|
90
173
|
:list_formatter => @list_formatter,
|
174
|
+
:log => log,
|
175
|
+
:max_failed_logins => @max_failed_logins,
|
91
176
|
:response_delay => response_delay,
|
92
|
-
:tls => @tls,
|
93
|
-
:auth_level => @auth_level,
|
94
|
-
:session_timeout => @session_timeout,
|
95
177
|
:server_name => @server_name,
|
96
178
|
:server_version => @server_version,
|
97
|
-
:
|
98
|
-
:
|
179
|
+
:session_timeout => @session_timeout,
|
180
|
+
:socket => socket,
|
181
|
+
:tls => @tls).run
|
99
182
|
end
|
100
183
|
|
101
184
|
def read_version_file
|