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
@@ -0,0 +1,13 @@
1
+ Feature: Abort
2
+
3
+ As a client
4
+ I want to know this command is not supported
5
+ So that I can avoid using it
6
+
7
+ Background:
8
+ Given the test server is started
9
+
10
+ Scenario: Unimplemented
11
+ Given a successful connection
12
+ When the client sends command "ABOR"
13
+ Then the server returns an unimplemented command error
@@ -11,15 +11,3 @@ Feature: Command Errors
11
11
  Given a successful connection
12
12
  When the client sends command "foo"
13
13
  Then the server returns a command unrecognized error
14
-
15
- Scenario Outline: Unimplemented command
16
- Given a successful connection
17
- When the client sends command "<command>"
18
- Then the server returns an unimplemented command error
19
- Examples:
20
- | command |
21
- | ABOR |
22
- | REIN |
23
- | REST |
24
- | SITE |
25
- | SMNT |
@@ -0,0 +1,23 @@
1
+ Feature: Delay After Failed Login
2
+
3
+ As an administrator
4
+ I want to make brute force attacks less efficient
5
+ So that an attacker doesn't gain access
6
+
7
+ Scenario: Failed login attempts
8
+ Given the test server has a failed login delay of 0.2 seconds
9
+ And the test server is started
10
+ Given the client connects
11
+ And the client logs in with bad user
12
+ And the client logs in with bad user
13
+ And the client logs in
14
+ Then it should take at least 0.4 seconds
15
+
16
+ Scenario: Failed login attempts
17
+ Given the test server has a failed login delay of 0.0 seconds
18
+ And the test server is started
19
+ Given the client connects
20
+ And the client logs in with bad user
21
+ And the client logs in with bad user
22
+ And the client logs in
23
+ Then it should take less than 0.4 seconds
@@ -0,0 +1,25 @@
1
+ Feature: Disconnect After Failed Logins
2
+
3
+ As an administrator
4
+ I want to make brute force attacks less efficient
5
+ So that an attacker doesn't gain access
6
+
7
+ Scenario: Disconnected after maximum failed attempts
8
+ Given the test server has a max of 3 failed login attempts
9
+ And the test server is started
10
+ Given the client connects
11
+ And the client logs in with bad user
12
+ And the client logs in with bad user
13
+ When the client logs in with bad user
14
+ Then the server returns a server unavailable error
15
+ And the client should not be connected
16
+
17
+ Scenario: No maximum configured
18
+ Given the test server has no max failed login attempts
19
+ And the test server is started
20
+ Given the client connects
21
+ And the client logs in with bad user
22
+ And the client logs in with bad user
23
+ And the client logs in with bad user
24
+ Then the server returns a login incorrect error
25
+ And the client should be connected
@@ -0,0 +1,26 @@
1
+ Feature: Features
2
+
3
+ As a client
4
+ I want to know what FTP extension the server supports
5
+ So that I can use them without trial-and-error
6
+
7
+ Background:
8
+
9
+ Scenario: TLS Disabled
10
+ Given the test server is started
11
+ And the client connects
12
+ When the client successfully requests features
13
+ Then the response should not include TLS features
14
+
15
+ Scenario: TLS Enabled
16
+ Given the test server has TLS mode "explicit"
17
+ And the test server is started
18
+ And the client connects
19
+ When the client successfully requests features
20
+ Then the response should include TLS features
21
+
22
+ Scenario: Argument given
23
+ Given the test server is started
24
+ And the client connects
25
+ When the client sends "FEAT FOO"
26
+ Then the server returns a syntax error
@@ -0,0 +1,39 @@
1
+ Feature: Max Connections
2
+
3
+ As an administrator
4
+ I want to limit the number of connections
5
+ To prevent overload
6
+
7
+ Scenario: Total connections
8
+ Given the test server has max_connections set to 2
9
+ And the test server is started
10
+ And the 1st client connects
11
+ And the 2nd client connects
12
+ When the 3rd client tries to connect
13
+ Then the server returns a too many connections error
14
+
15
+ Scenario: Connections per user
16
+ And the test server has max_connections_per_ip set to 1
17
+ And the test server is started
18
+ And the 1st client connects from 127.0.0.1
19
+ And the 2nd client connects from 127.0.0.2
20
+ When the 3rd client tries to connect from 127.0.0.2
21
+ Then the server returns a too many connections error
22
+
23
+ Scenario: TLS
24
+ Given the test server has max_connections set to 2
25
+ And the test server has TLS mode "explicit"
26
+ And the test server is started
27
+ And the 1st client connects
28
+ And the 2nd client connects
29
+ When the 3rd client tries to connect
30
+ Then the server returns a too many connections error
31
+
32
+ Scenario: Connections per user, TLS
33
+ And the test server has max_connections_per_ip set to 1
34
+ And the test server has TLS mode "explicit"
35
+ And the test server is started
36
+ And the 1st client connects from 127.0.0.1
37
+ And the 2nd client connects from 127.0.0.2
38
+ When the 3rd client tries to connect from 127.0.0.2
39
+ Then the server returns a too many connections error
@@ -0,0 +1,17 @@
1
+ Feature: Options
2
+
3
+ As a client
4
+ I want to know set options
5
+ To tailor the server's behavior
6
+
7
+ Background:
8
+ Given the test server is started
9
+ And the client connects
10
+
11
+ Scenario: No argument
12
+ When the client sends "OPTS"
13
+ Then the server returns a syntax error
14
+
15
+ Scenario: Unknown option command
16
+ When the client sets option "ABC"
17
+ Then the server returns a bad option error
@@ -8,7 +8,7 @@ Feature: Put TLS
8
8
  Given the test server has TLS mode "explicit"
9
9
  And the test server is started
10
10
 
11
- Scenario: TLS
11
+ Scenario: TLS, Active
12
12
  pending "TLS not supported in active mode (see README)"
13
13
 
14
14
  Scenario: TLS, Passive
@@ -0,0 +1,13 @@
1
+ Feature: Reinitialize
2
+
3
+ As a client
4
+ I want to know this command is not supported
5
+ So that I can avoid using it
6
+
7
+ Background:
8
+ Given the test server is started
9
+
10
+ Scenario: Unimplemented
11
+ Given a successful connection
12
+ When the client sends command "REIN"
13
+ Then the server returns an unimplemented command error
@@ -0,0 +1,13 @@
1
+ Feature: Site
2
+
3
+ As a client
4
+ I want to know this command is not supported
5
+ So that I can avoid using it
6
+
7
+ Background:
8
+ Given the test server is started
9
+
10
+ Scenario: Unimplemented
11
+ Given a successful connection
12
+ When the client sends command "SITE"
13
+ Then the server returns an unimplemented command error
@@ -15,5 +15,4 @@ Feature: Status
15
15
  Scenario: Server status
16
16
  Given a successful login
17
17
  When the client successfully requests status
18
- Then the server returns its name
19
- And the server returns its version
18
+ Then the server returns its title
@@ -39,3 +39,23 @@ end
39
39
  Given /^the test server allows low data ports$/ do
40
40
  server.allow_low_data_ports = true
41
41
  end
42
+
43
+ Given /^the test server has max_connections set to (\d+)$/ do |s|
44
+ server.max_connections = s.to_i
45
+ end
46
+
47
+ Given /^the test server has max_connections_per_ip set to (\d+)$/ do |s|
48
+ server.max_connections_per_ip = s.to_i
49
+ end
50
+
51
+ Given /^the test server has no max failed login attempts$/ do
52
+ server.max_failed_logins = nil
53
+ end
54
+
55
+ Given /^the test server has a max of (\d+) failed login attempts$/ do |s|
56
+ server.max_failed_logins = s.to_i
57
+ end
58
+
59
+ Given /^the test server has a failed login delay of (\S+) seconds$/ do |s|
60
+ server.failed_login_delay = s.to_f
61
+ end
@@ -0,0 +1,13 @@
1
+ Feature: Structure Mount
2
+
3
+ As a client
4
+ I want to know this command is not supported
5
+ So that I can avoid using it
6
+
7
+ Background:
8
+ Given the test server is started
9
+
10
+ Scenario: Unimplemented
11
+ Given a successful connection
12
+ When the client sends command "SMNT"
13
+ Then the server returns an unimplemented command error
@@ -9,18 +9,18 @@ Feature: Port
9
9
  And the test server is started
10
10
  And a successful login
11
11
  When the client is idle for 0.6 seconds
12
- Then the client is not connected
12
+ Then the client should not be connected
13
13
 
14
14
  Scenario: Session not idle too long
15
15
  Given the test server has session timeout set to 0.5 seconds
16
16
  And the test server is started
17
17
  And a successful login
18
18
  When the client is idle for 0 seconds
19
- Then the client is connected
19
+ Then the client should be connected
20
20
 
21
21
  Scenario: Timeout disabled
22
22
  Given the test server has session timeout disabled
23
23
  And the test server is started
24
24
  And a successful login
25
25
  When the client is idle for 0.6 seconds
26
- Then the client is connected
26
+ Then the client should be connected
@@ -6,10 +6,10 @@ end
6
6
 
7
7
  When /^the client successfully appends text "(.*?)" onto "(.*?)"$/ do
8
8
  |local_path, remote_path|
9
- @client.append_text local_path, remote_path
9
+ client.append_text local_path, remote_path
10
10
  end
11
11
 
12
12
  When /^the client successfully appends binary "(.*?)" onto "(.*?)"$/ do
13
13
  |local_path, remote_path|
14
- @client.append_binary local_path, remote_path
14
+ client.append_binary local_path, remote_path
15
15
  end
@@ -1,14 +1,24 @@
1
- def client_variable_name(client_name)
2
- var = '@' + [
3
- client_name,
4
- 'client',
5
- ].compact.map(&:strip).join('_')
6
- end
1
+ require 'singleton'
2
+
3
+ class Clients
4
+
5
+ include Singleton
6
+
7
+ def initialize
8
+ @clients = {}
9
+ end
10
+
11
+ def [](client_name)
12
+ @clients[client_name] ||= TestClient.new
13
+ end
14
+
15
+ def close
16
+ @clients.values.each(&:close)
17
+ end
7
18
 
8
- def set_client(client_name, client)
9
- instance_variable_set client_variable_name(client_name), client
10
19
  end
11
20
 
12
21
  def client(client_name = nil)
13
- instance_variable_get(client_variable_name(client_name))
22
+ client_name ||= 'client'
23
+ Clients.instance[client_name]
14
24
  end
@@ -7,7 +7,7 @@ Then /^the remote file "(.*?)" should( exactly)? match the local file$/ do
7
7
  |remote_path, exactly|
8
8
  local_path = File.basename(remote_path)
9
9
  remote_contents = server.file_contents(remote_path)
10
- local_contents = @client.file_contents(local_path)
10
+ local_contents = client.file_contents(local_path)
11
11
  remote_contents = unix_line_endings(exactly, remote_contents)
12
12
  local_contents = unix_line_endings(exactly, local_contents)
13
13
  remote_contents.should == local_contents
@@ -17,7 +17,7 @@ Then /^the local file "(.*?)" should( exactly)? match the remote file$/ do
17
17
  |local_path, exactly|
18
18
  remote_path = local_path
19
19
  remote_contents = server.file_contents(remote_path)
20
- local_contents = @client.file_contents(local_path)
20
+ local_contents = client.file_contents(local_path)
21
21
  remote_contents = unix_line_endings(exactly, remote_contents)
22
22
  local_contents = unix_line_endings(exactly, local_contents)
23
23
  local_contents.should == remote_contents
@@ -1,14 +1,14 @@
1
1
  Given /^the client has file "(.*?)"$/ do |local_path|
2
- @client.add_file local_path
2
+ client.add_file local_path
3
3
  end
4
4
 
5
5
  Then /^the local file "(.*?)" should have (unix|windows) line endings$/ do
6
6
  |local_path, line_ending_type|
7
- line_ending_type(@client.file_contents(local_path)).should ==
7
+ line_ending_type(client.file_contents(local_path)).should ==
8
8
  line_ending_type.to_sym
9
9
  end
10
10
 
11
11
  Then /^the local file "(.*?)" should match its template$/ do |local_path|
12
- @client.template(local_path).should ==
13
- @client.file_contents(local_path)
12
+ client.template(local_path).should ==
13
+ client.file_contents(local_path)
14
14
  end
@@ -1,5 +1,5 @@
1
1
  When /^the client sends command "(.*?)"$/ do |command|
2
2
  capture_error do
3
- @client.raw command
3
+ client.raw command
4
4
  end
5
5
  end
@@ -4,11 +4,34 @@ require 'net/ftp'
4
4
  When /^the( \w+)? client connects(?: with (\w+) TLS)?$/ do
5
5
  |client_name, tls_mode|
6
6
  tls_mode ||= :off
7
- client = TestClient.new(:tls => tls_mode.to_sym)
8
- client.connect(server.host, server.port)
9
- set_client client_name, client
7
+ client(client_name).tls_mode = tls_mode.to_sym
8
+ client(client_name).start
9
+ client(client_name).connect(server.host, server.port)
10
10
  end
11
11
 
12
- After do
13
- @client.close if @client
12
+ When /^the (\d+)rd client tries to connect$/ do |client_name|
13
+ client(client_name).start
14
+ capture_error do
15
+ client(client_name).connect(server.host, server.port)
16
+ end
17
+ end
18
+
19
+ When /^the (\S+) client connects from (\S+)$/ do
20
+ |client_name, source_ip|
21
+ client(client_name).connect_from(source_ip, server.host, server.port)
22
+ end
23
+
24
+ When /^the (\S+) client tries to connect from (\S+)$/ do
25
+ |client_name, source_ip|
26
+ capture_error do
27
+ step "the #{client_name} client connects from #{source_ip}"
28
+ end
29
+ end
30
+
31
+ Then /^the client should be connected$/ do
32
+ client.should be_connected
33
+ end
34
+
35
+ Then /^the client should not be connected$/ do
36
+ client.should_not be_connected
14
37
  end
@@ -5,11 +5,11 @@ When /^the client deletes "(.*?)"$/ do |path|
5
5
  end
6
6
 
7
7
  When /^the client successfully deletes "(.*?)"$/ do |path|
8
- @client.delete path
8
+ client.delete path
9
9
  end
10
10
 
11
11
  When /^the client deletes with no path$/ do
12
12
  capture_error do
13
- @client.raw 'DELE'
13
+ client.raw 'DELE'
14
14
  end
15
15
  end
@@ -10,17 +10,17 @@ end
10
10
  # ensure that CDUP is sent and therefore tested.
11
11
 
12
12
  When /^the client successfully cd's up$/ do
13
- @client.raw 'CDUP'
13
+ client.raw 'CDUP'
14
14
  end
15
15
 
16
16
  When /^the client successfully cd's to "(.*?)"$/ do |path|
17
- @client.chdir path
17
+ client.chdir path
18
18
  end
19
19
 
20
20
  Then /^the current directory should be "(.*?)"$/ do |path|
21
- @client.pwd.should == path
21
+ client.pwd.should == path
22
22
  end
23
23
 
24
24
  Then /^the XPWD directory should be "(.*?)"$/ do |path|
25
- @client.xpwd.should == path
25
+ client.xpwd.should == path
26
26
  end