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.
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