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