ftpd 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/Changelog.md +25 -3
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +0 -5
  4. data/README.md +109 -51
  5. data/VERSION +1 -1
  6. data/doc/benchmarks.md +4 -3
  7. data/doc/references.md +1 -1
  8. data/doc/rfc-compliance.md +18 -18
  9. data/examples/example.rb +1 -0
  10. data/features/ftp_server/abort.feature +13 -0
  11. data/features/ftp_server/command_errors.feature +0 -12
  12. data/features/ftp_server/delay_after_failed_login.feature +23 -0
  13. data/features/ftp_server/disconnect_after_failed_logins.feature +25 -0
  14. data/features/ftp_server/features.feature +26 -0
  15. data/features/ftp_server/max_connections.feature +39 -0
  16. data/features/ftp_server/options.feature +17 -0
  17. data/features/ftp_server/put_tls.feature +1 -1
  18. data/features/ftp_server/reinitialize.feature +13 -0
  19. data/features/ftp_server/site.feature +13 -0
  20. data/features/ftp_server/status.feature +1 -2
  21. data/features/ftp_server/step_definitions/test_server.rb +20 -0
  22. data/features/ftp_server/structure_mount.feature +13 -0
  23. data/features/ftp_server/timeout.feature +3 -3
  24. data/features/step_definitions/append.rb +2 -2
  25. data/features/step_definitions/client.rb +19 -9
  26. data/features/step_definitions/client_and_server_files.rb +2 -2
  27. data/features/step_definitions/client_files.rb +4 -4
  28. data/features/step_definitions/command.rb +1 -1
  29. data/features/step_definitions/connect.rb +28 -5
  30. data/features/step_definitions/delete.rb +2 -2
  31. data/features/step_definitions/directory_navigation.rb +4 -4
  32. data/features/step_definitions/error_replies.rb +12 -0
  33. data/features/step_definitions/features.rb +21 -0
  34. data/features/step_definitions/file_structure.rb +2 -2
  35. data/features/step_definitions/generic_send.rb +1 -1
  36. data/features/step_definitions/get.rb +2 -2
  37. data/features/step_definitions/help.rb +1 -1
  38. data/features/step_definitions/invalid_commands.rb +2 -2
  39. data/features/step_definitions/list.rb +2 -2
  40. data/features/step_definitions/login.rb +3 -3
  41. data/features/step_definitions/mkdir.rb +1 -1
  42. data/features/step_definitions/mode.rb +2 -2
  43. data/features/step_definitions/options.rb +9 -0
  44. data/features/step_definitions/passive.rb +1 -1
  45. data/features/step_definitions/port.rb +1 -1
  46. data/features/step_definitions/put.rb +3 -3
  47. data/features/step_definitions/quit.rb +2 -2
  48. data/features/step_definitions/rename.rb +1 -1
  49. data/features/step_definitions/rmdir.rb +1 -1
  50. data/features/step_definitions/server_title.rb +12 -0
  51. data/features/step_definitions/status.rb +1 -9
  52. data/features/step_definitions/system.rb +1 -1
  53. data/features/step_definitions/timing.rb +19 -0
  54. data/features/step_definitions/type.rb +2 -2
  55. data/features/support/test_client.rb +62 -7
  56. data/features/support/test_server.rb +4 -0
  57. data/ftpd.gemspec +21 -9
  58. data/lib/ftpd.rb +4 -0
  59. data/lib/ftpd/command_sequence_checker.rb +4 -2
  60. data/lib/ftpd/config.rb +13 -0
  61. data/lib/ftpd/connection_throttle.rb +56 -0
  62. data/lib/ftpd/connection_tracker.rb +110 -0
  63. data/lib/ftpd/disk_file_system.rb +2 -2
  64. data/lib/ftpd/ftp_server.rb +118 -35
  65. data/lib/ftpd/server.rb +27 -3
  66. data/lib/ftpd/session.rb +84 -25
  67. data/lib/ftpd/tls_server.rb +11 -5
  68. data/rake_tasks/cucumber.rake +1 -0
  69. data/rake_tasks/jeweler.rake +1 -1
  70. data/spec/connection_throttle_spec.rb +96 -0
  71. data/spec/connection_tracker_spec.rb +126 -0
  72. data/spec/spec_helper.rb +1 -0
  73. metadata +22 -23
  74. data/config/cucumber.yml +0 -2
  75. data/features/step_definitions/stop_server.rb +0 -3
  76. 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='
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ftpd"
8
- s.version = "0.5.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-10"
13
- s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and is unconditionally complaint per RFC-1123. It an be used as part of a test fixture or embedded in a program."
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/timeout.rb",
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"])
@@ -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, unless
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.
@@ -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
@@ -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
- # The number of seconds to delay before replying. This is for
10
- # testing, when you need to test, for example, client timeouts.
11
- # Defaults to 0 (no delay).
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
- # Change to this attribute only take effect for new sessions.
14
-
15
- attr_accessor :response_delay
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 :list_formatter
20
+ attr_accessor :allow_low_data_ports
22
21
 
23
- # @return [Integer] The authentication level
24
- # One of:
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 session timeout. When a session is awaiting a command, if
32
- # one is not received in this many seconds, the session is
33
- # disconnected. Defaults to {DEFAULT_SESSION_TIMEOUT}. If nil,
34
- # then timeout is disabled.
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 :session_timeout
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 logger. Defaults to nil (no logging).
50
- # @return [Logger]
51
-
52
- attr_accessor :log
53
-
54
- # Allow PORT command to specify data ports below 1024. Defaults
55
- # to false. Setting this to true makes it easier for an attacker
56
- # to use the server to attack another server. See RFC 2577
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 :allow_low_data_ports
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
- Session.new(:socket => socket,
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
- :log => log,
98
- :allow_low_data_ports => allow_low_data_ports).run
179
+ :session_timeout => @session_timeout,
180
+ :socket => socket,
181
+ :tls => @tls).run
99
182
  end
100
183
 
101
184
  def read_version_file