ftpd 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,4 +23,3 @@ Feature: Command Errors
23
23
  | REST |
24
24
  | SITE |
25
25
  | SMNT |
26
- | STAT |
@@ -0,0 +1,11 @@
1
+ Feature: Logging
2
+
3
+ As a programmer
4
+ I want to see logging output
5
+ So that I can fix FTP protocol problems
6
+
7
+ Scenario: Logging enabled
8
+ Given the test server has logging enabled
9
+ And the test server is started
10
+ And a successful login
11
+ Then the server should have written log output
@@ -7,6 +7,21 @@ Feature: Port
7
7
  Background:
8
8
  Given the test server is started
9
9
 
10
+ Scenario: Port 1024
11
+ Given a successful login
12
+ Then the client successfully sends "PORT 1,2,3,4,4,0"
13
+
14
+ Scenario: Port 1023; low ports disallowed
15
+ Given the test server disallows low data ports
16
+ And a successful login
17
+ When the client sends "PORT 1,2,3,4,3,255"
18
+ Then the server returns an unimplemented parameter error
19
+
20
+ Scenario: Port 1023; low ports allowed
21
+ Given the test server allows low data ports
22
+ And a successful login
23
+ Then the client successfully sends "PORT 1,2,3,4,3,255"
24
+
10
25
  Scenario: Not logged in
11
26
  Given a successful connection
12
27
  When the client sends PORT "1,2,3,4,5,6"
@@ -0,0 +1,19 @@
1
+ Feature: Status
2
+
3
+ As a client
4
+ I want server status
5
+ To know what state the server is in
6
+
7
+ Background:
8
+ Given the test server is started
9
+
10
+ Scenario: Not logged in
11
+ Given a successful connection
12
+ When the client requests status
13
+ Then the server returns a not logged in error
14
+
15
+ Scenario: Server status
16
+ Given a successful login
17
+ When the client successfully requests status
18
+ Then the server returns its name
19
+ And the server returns its version
@@ -0,0 +1,8 @@
1
+ Then /^the server should have written( no)? log output$/ do |neg|
2
+ method = if neg
3
+ :should
4
+ else
5
+ :should_not
6
+ end
7
+ server.log_output.send(method) == ''
8
+ end
@@ -10,8 +10,8 @@ Given /^the test server has TLS mode "(\w+)"$/ do |mode|
10
10
  server.tls = mode.to_sym
11
11
  end
12
12
 
13
- Given /^the test server has debug (enabled|disabled)$/ do |state|
14
- server.debug = state == 'enabled'
13
+ Given /^the test server has logging (enabled|disabled)$/ do |state|
14
+ server.logging = state == 'enabled'
15
15
  end
16
16
 
17
17
  Given /^the test server lacks (\w+)$/ do |feature|
@@ -22,3 +22,20 @@ Given /^the test server has auth level "(.*?)"$/ do |auth_level|
22
22
  auth_level = Ftpd.const_get(auth_level)
23
23
  server.auth_level = auth_level
24
24
  end
25
+
26
+ Given /^the test server has session timeout set to (\S+) seconds$/ do
27
+ |timeout|
28
+ server.session_timeout = timeout.to_f
29
+ end
30
+
31
+ Given /^the test server has session timeout disabled$/ do
32
+ server.session_timeout = nil
33
+ end
34
+
35
+ Given /^the test server disallows low data ports$/ do
36
+ server.allow_low_data_ports = false
37
+ end
38
+
39
+ Given /^the test server allows low data ports$/ do
40
+ server.allow_low_data_ports = true
41
+ end
@@ -0,0 +1,26 @@
1
+ Feature: Port
2
+
3
+ As a programmer
4
+ I want idle sessions to timeout and disconnect
5
+ So that I can claim RFC compliance
6
+
7
+ Scenario: Session idle too long
8
+ Given the test server has session timeout set to 0.5 seconds
9
+ And the test server is started
10
+ And a successful login
11
+ When the client is idle for 0.6 seconds
12
+ Then the client is not connected
13
+
14
+ Scenario: Session not idle too long
15
+ Given the test server has session timeout set to 0.5 seconds
16
+ And the test server is started
17
+ And a successful login
18
+ When the client is idle for 0 seconds
19
+ Then the client is connected
20
+
21
+ Scenario: Timeout disabled
22
+ Given the test server has session timeout disabled
23
+ And the test server is started
24
+ And a successful login
25
+ When the client is idle for 0.6 seconds
26
+ Then the client is connected
@@ -73,6 +73,11 @@ Then /^the server returns a format not implemented error$/ do
73
73
  step 'the server returns a "504 Format not implemented" error'
74
74
  end
75
75
 
76
+ Then /^the server returns an unimplemented parameter error$/ do
77
+ step('the server returns a "504 Command not '\
78
+ 'implemented for that parameter" error')
79
+ end
80
+
76
81
  Then /^the server returns a command unrecognized error$/ do
77
82
  step 'the server returns a "500 Syntax error, command unrecognized" error'
78
83
  end
@@ -0,0 +1,17 @@
1
+ When /^the client successfully requests status$/ do
2
+ @status = @client.status
3
+ end
4
+
5
+ When /^the client requests status$/ do
6
+ capture_error do
7
+ step 'the client successfully requests status'
8
+ end
9
+ end
10
+
11
+ Then /^the server returns its name$/ do
12
+ @status.should include @server.server_name
13
+ end
14
+
15
+ Then /^the server returns its version$/ do
16
+ @status.should =~ /\b\d+\.\d+\.\d+\b/
17
+ end
@@ -0,0 +1,11 @@
1
+ When /^the client is idle for (\S+) seconds$/ do |seconds|
2
+ sleep seconds.to_f
3
+ end
4
+
5
+ Then /^the client is connected$/ do
6
+ @client.should be_connected
7
+ end
8
+
9
+ Then /^the client is not connected$/ do
10
+ @client.should_not be_connected
11
+ end
@@ -30,9 +30,10 @@ class TestClient
30
30
  :noop,
31
31
  :passive=,
32
32
  :pwd,
33
+ :quit,
33
34
  :rename,
34
35
  :rmdir,
35
- :quit,
36
+ :status,
36
37
  :system
37
38
 
38
39
  def raw(*command)
@@ -91,6 +92,17 @@ class TestClient
91
92
  end
92
93
  end
93
94
 
95
+ def connected?
96
+ begin
97
+ @ftp.noop
98
+ true
99
+ rescue Net::FTPTempError => e
100
+ !!e.to_s =~ /^421/
101
+ rescue EOFError
102
+ false
103
+ end
104
+ end
105
+
94
106
  private
95
107
 
96
108
  RAW_METHOD_REGEX = /^send_(.*)$/
@@ -1,5 +1,6 @@
1
1
  require 'fileutils'
2
2
  require 'forwardable'
3
+ require 'stringio'
3
4
  require 'tempfile'
4
5
 
5
6
  require File.expand_path('test_server_files',
@@ -202,22 +203,24 @@ class TestServer
202
203
  include Ftpd::InsecureCertificate
203
204
  include TestServerFiles
204
205
 
206
+ attr_writer :logging
207
+
205
208
  def initialize
206
209
  @temp_dir = Ftpd::TempDir.make
207
- @debug_file = Tempfile.new('ftp-server-debug-output')
208
- @debug_file.close
210
+ @log_device = StringIO.new
209
211
  @driver = TestServerDriver.new(@temp_dir)
210
212
  @server = Ftpd::FtpServer.new(@driver)
211
213
  @server.certfile_path = insecure_certfile_path
212
- @server.debug_path = @debug_file.path
213
214
  @templates = TestFileTemplates.new
214
- self.debug = false
215
215
  self.tls = :off
216
216
  end
217
217
 
218
+ def_delegator :@server, :'allow_low_data_ports='
218
219
  def_delegator :@server, :'auth_level'
219
220
  def_delegator :@server, :'auth_level='
220
- def_delegator :@server, :'debug='
221
+ def_delegator :@server, :'server_name'
222
+ def_delegator :@server, :'server_name='
223
+ def_delegator :@server, :'session_timeout='
221
224
  def_delegator :@server, :'tls='
222
225
 
223
226
  def_delegator :@driver, :'append='
@@ -229,7 +232,12 @@ class TestServer
229
232
  def_delegator :@driver, :'rename='
230
233
  def_delegator :@driver, :'write='
231
234
 
235
+ def log_output
236
+ @log_device.string
237
+ end
238
+
232
239
  def start
240
+ @server.log = make_log
233
241
  @server.start
234
242
  end
235
243
 
@@ -237,10 +245,6 @@ class TestServer
237
245
  @server.stop
238
246
  end
239
247
 
240
- def debug_output
241
- IO.read(@debug_file.path)
242
- end
243
-
244
248
  def host
245
249
  'localhost'
246
250
  end
@@ -271,4 +275,14 @@ class TestServer
271
275
  @temp_dir
272
276
  end
273
277
 
278
+ def make_log
279
+ if @logging
280
+ Logger.new(@log_device)
281
+ elsif ENV['FTPD_DEBUG'].to_i != 0
282
+ Logger.new($stdout)
283
+ else
284
+ nil
285
+ end
286
+ end
287
+
274
288
  end
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ftpd"
8
- s.version = "0.4.0"
8
+ s.version = "0.5.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-06"
13
- s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and most of the commands specified in RFC 969. It an be used as part of a test fixture or embedded in a program."
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."
14
14
  s.email = "wconrad@yagni.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.md",
@@ -25,6 +25,8 @@ Gem::Specification.new do |s|
25
25
  "README.md",
26
26
  "Rakefile",
27
27
  "VERSION",
28
+ "config/cucumber.yml",
29
+ "doc/benchmarks.md",
28
30
  "doc/references.md",
29
31
  "doc/rfc-compliance.md",
30
32
  "examples/example.rb",
@@ -38,7 +40,6 @@ Gem::Specification.new do |s|
38
40
  "features/ftp_server/cdup.feature",
39
41
  "features/ftp_server/command_errors.feature",
40
42
  "features/ftp_server/concurrent_sessions.feature",
41
- "features/ftp_server/debug.feature",
42
43
  "features/ftp_server/delete.feature",
43
44
  "features/ftp_server/directory_navigation.feature",
44
45
  "features/ftp_server/file_structure.feature",
@@ -49,6 +50,7 @@ Gem::Specification.new do |s|
49
50
  "features/ftp_server/invertability.feature",
50
51
  "features/ftp_server/list.feature",
51
52
  "features/ftp_server/list_tls.feature",
53
+ "features/ftp_server/logging.feature",
52
54
  "features/ftp_server/login_auth_level_account.feature",
53
55
  "features/ftp_server/login_auth_level_password.feature",
54
56
  "features/ftp_server/login_auth_level_user.feature",
@@ -64,10 +66,12 @@ Gem::Specification.new do |s|
64
66
  "features/ftp_server/quit.feature",
65
67
  "features/ftp_server/rename.feature",
66
68
  "features/ftp_server/rmdir.feature",
67
- "features/ftp_server/step_definitions/debug.rb",
69
+ "features/ftp_server/status.feature",
70
+ "features/ftp_server/step_definitions/logging.rb",
68
71
  "features/ftp_server/step_definitions/test_server.rb",
69
72
  "features/ftp_server/syntax_errors.feature",
70
73
  "features/ftp_server/syst.feature",
74
+ "features/ftp_server/timeout.feature",
71
75
  "features/ftp_server/type.feature",
72
76
  "features/step_definitions/append.rb",
73
77
  "features/step_definitions/client.rb",
@@ -97,9 +101,11 @@ Gem::Specification.new do |s|
97
101
  "features/step_definitions/rename.rb",
98
102
  "features/step_definitions/rmdir.rb",
99
103
  "features/step_definitions/server_files.rb",
104
+ "features/step_definitions/status.rb",
100
105
  "features/step_definitions/stop_server.rb",
101
106
  "features/step_definitions/success_replies.rb",
102
107
  "features/step_definitions/system.rb",
108
+ "features/step_definitions/timeout.rb",
103
109
  "features/step_definitions/type.rb",
104
110
  "features/support/env.rb",
105
111
  "features/support/example_server.rb",
@@ -125,9 +131,11 @@ Gem::Specification.new do |s|
125
131
  "lib/ftpd/insecure_certificate.rb",
126
132
  "lib/ftpd/list_format/eplf.rb",
127
133
  "lib/ftpd/list_format/ls.rb",
134
+ "lib/ftpd/null_logger.rb",
128
135
  "lib/ftpd/read_only_disk_file_system.rb",
129
136
  "lib/ftpd/server.rb",
130
137
  "lib/ftpd/session.rb",
138
+ "lib/ftpd/telnet.rb",
131
139
  "lib/ftpd/temp_dir.rb",
132
140
  "lib/ftpd/tls_server.rb",
133
141
  "lib/ftpd/translate_exceptions.rb",
@@ -144,7 +152,9 @@ Gem::Specification.new do |s|
144
152
  "spec/file_system_error_translator_spec.rb",
145
153
  "spec/list_format/eplf_spec.rb",
146
154
  "spec/list_format/ls_spec.rb",
155
+ "spec/null_logger_spec.rb",
147
156
  "spec/spec_helper.rb",
157
+ "spec/telnet_spec.rb",
148
158
  "spec/translate_exceptions_spec.rb"
149
159
  ]
150
160
  s.homepage = "http://github.com/wconrad/ftpd"
@@ -160,6 +170,7 @@ Gem::Specification.new do |s|
160
170
  s.add_runtime_dependency(%q<memoizer>, ["~> 1.0.1"])
161
171
  s.add_development_dependency(%q<cucumber>, [">= 0"])
162
172
  s.add_development_dependency(%q<double-bag-ftps>, [">= 0"])
173
+ s.add_development_dependency(%q<fuubar-cucumber>, [">= 0"])
163
174
  s.add_development_dependency(%q<jeweler>, [">= 0"])
164
175
  s.add_development_dependency(%q<rake>, [">= 0"])
165
176
  s.add_development_dependency(%q<redcarpet>, [">= 0"])
@@ -170,6 +181,7 @@ Gem::Specification.new do |s|
170
181
  s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
171
182
  s.add_dependency(%q<cucumber>, [">= 0"])
172
183
  s.add_dependency(%q<double-bag-ftps>, [">= 0"])
184
+ s.add_dependency(%q<fuubar-cucumber>, [">= 0"])
173
185
  s.add_dependency(%q<jeweler>, [">= 0"])
174
186
  s.add_dependency(%q<rake>, [">= 0"])
175
187
  s.add_dependency(%q<redcarpet>, [">= 0"])
@@ -181,6 +193,7 @@ Gem::Specification.new do |s|
181
193
  s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
182
194
  s.add_dependency(%q<cucumber>, [">= 0"])
183
195
  s.add_dependency(%q<double-bag-ftps>, [">= 0"])
196
+ s.add_dependency(%q<fuubar-cucumber>, [">= 0"])
184
197
  s.add_dependency(%q<jeweler>, [">= 0"])
185
198
  s.add_dependency(%q<rake>, [">= 0"])
186
199
  s.add_dependency(%q<redcarpet>, [">= 0"])
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require 'logger'
2
3
  require 'memoizer'
3
4
  require 'openssl'
4
5
  require 'pathname'
@@ -20,9 +21,11 @@ module Ftpd
20
21
  autoload :FileSystemMethodMissing, 'ftpd/file_system_method_missing'
21
22
  autoload :FtpServer, 'ftpd/ftp_server'
22
23
  autoload :InsecureCertificate, 'ftpd/insecure_certificate'
24
+ autoload :NullLogger, 'ftpd/null_logger'
23
25
  autoload :ReadOnlyDiskFileSystem, 'ftpd/read_only_disk_file_system'
24
26
  autoload :Server, 'ftpd/server'
25
27
  autoload :Session, 'ftpd/session'
28
+ autoload :Telnet, 'ftpd/telnet'
26
29
  autoload :TempDir, 'ftpd/temp_dir'
27
30
  autoload :TlsServer, 'ftpd/tls_server'
28
31
  autoload :TranslateExceptions, 'ftpd/translate_exceptions'
@@ -3,19 +3,8 @@
3
3
  module Ftpd
4
4
  class FtpServer < TlsServer
5
5
 
6
- # If truthy, emit debug information (such as replies received and
7
- # responses sent) to the file named by #debug_path.
8
- #
9
- # Change to this attribute only take effect for new sessions.
10
-
11
- attr_accessor :debug
12
-
13
- # The path to which to write debug information. Defaults to
14
- # '/dev/stdout'
15
- #
16
- # Change to this attribute only take effect for new sessions.
17
-
18
- attr_accessor :debug_path
6
+ DEFAULT_SERVER_NAME = 'wconrad/ftpd'
7
+ DEFAULT_SESSION_TIMEOUT = 300 # seconds
19
8
 
20
9
  # The number of seconds to delay before replying. This is for
21
10
  # testing, when you need to test, for example, client timeouts.
@@ -39,6 +28,37 @@ module Ftpd
39
28
 
40
29
  attr_accessor :auth_level
41
30
 
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.
35
+ # @return [Numeric]
36
+
37
+ attr_accessor :session_timeout
38
+
39
+ # The server's name, sent in a STAT reply. Defaults to
40
+ # {DEFAULT_SERVER_NAME}.
41
+
42
+ attr_accessor :server_name
43
+
44
+ # The server's version, sent in a STAT reply. Defaults to the
45
+ # contents of the VERSION file.
46
+
47
+ attr_accessor :server_version
48
+
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]
59
+
60
+ attr_accessor :allow_low_data_ports
61
+
42
62
  # Create a new FTP server. The server won't start until the
43
63
  # #start method is called.
44
64
  #
@@ -52,11 +72,14 @@ module Ftpd
52
72
  def initialize(driver)
53
73
  super()
54
74
  @driver = driver
55
- @debug_path = '/dev/stdout'
56
- @debug = false
57
75
  @response_delay = 0
58
76
  @list_formatter = ListFormat::Ls
59
77
  @auth_level = AUTH_PASSWORD
78
+ @session_timeout = 300
79
+ @server_name = DEFAULT_SERVER_NAME
80
+ @server_version = read_version_file
81
+ @allow_low_data_ports = false
82
+ @log = nil
60
83
  end
61
84
 
62
85
  private
@@ -64,12 +87,23 @@ module Ftpd
64
87
  def session(socket)
65
88
  Session.new(:socket => socket,
66
89
  :driver => @driver,
67
- :debug => @debug,
68
- :debug_path => debug_path,
69
90
  :list_formatter => @list_formatter,
70
91
  :response_delay => response_delay,
71
92
  :tls => @tls,
72
- :auth_level => @auth_level).run
93
+ :auth_level => @auth_level,
94
+ :session_timeout => @session_timeout,
95
+ :server_name => @server_name,
96
+ :server_version => @server_version,
97
+ :log => log,
98
+ :allow_low_data_ports => allow_low_data_ports).run
99
+ end
100
+
101
+ def read_version_file
102
+ File.open(version_file_path, 'r', &:read).strip
103
+ end
104
+
105
+ def version_file_path
106
+ File.expand_path('../../VERSION', File.dirname(__FILE__))
73
107
  end
74
108
 
75
109
  end