ftpd 0.3.2 → 0.4.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 (52) hide show
  1. data/.yardopts +1 -0
  2. data/Changelog.md +25 -1
  3. data/README.md +91 -10
  4. data/VERSION +1 -1
  5. data/doc/rfc-compliance.md +20 -20
  6. data/examples/example.rb +69 -11
  7. data/features/example/read_only.feature +63 -0
  8. data/features/ftp_server/append.feature +94 -0
  9. data/features/ftp_server/command_errors.feature +0 -2
  10. data/features/ftp_server/concurrent_sessions.feature +1 -1
  11. data/features/ftp_server/debug.feature +4 -2
  12. data/features/ftp_server/delete.feature +1 -1
  13. data/features/ftp_server/get.feature +1 -1
  14. data/features/ftp_server/get_tls.feature +2 -1
  15. data/features/ftp_server/implicit_tls.feature +2 -1
  16. data/features/ftp_server/invertability.feature +15 -0
  17. data/features/ftp_server/list.feature +1 -1
  18. data/features/ftp_server/list_tls.feature +2 -1
  19. data/features/ftp_server/login_auth_level_account.feature +51 -0
  20. data/features/ftp_server/{login.feature → login_auth_level_password.feature} +4 -8
  21. data/features/ftp_server/login_auth_level_user.feature +31 -0
  22. data/features/ftp_server/mkdir.feature +1 -1
  23. data/features/ftp_server/name_list.feature +1 -1
  24. data/features/ftp_server/name_list_tls.feature +2 -1
  25. data/features/ftp_server/put.feature +1 -1
  26. data/features/ftp_server/put_tls.feature +2 -1
  27. data/features/ftp_server/put_unique.feature +1 -1
  28. data/features/ftp_server/rename.feature +1 -1
  29. data/features/ftp_server/rmdir.feature +1 -1
  30. data/features/ftp_server/step_definitions/debug.rb +1 -1
  31. data/features/ftp_server/step_definitions/test_server.rb +16 -15
  32. data/features/ftp_server/type.feature +11 -8
  33. data/features/step_definitions/append.rb +15 -0
  34. data/features/step_definitions/client_and_server_files.rb +2 -2
  35. data/features/step_definitions/client_files.rb +5 -0
  36. data/features/step_definitions/connect.rb +1 -1
  37. data/features/step_definitions/error_replies.rb +0 -4
  38. data/features/step_definitions/login.rb +30 -20
  39. data/features/step_definitions/server_files.rb +20 -7
  40. data/features/support/example_server.rb +10 -2
  41. data/features/support/test_client.rb +18 -0
  42. data/features/support/test_file_templates.rb +1 -1
  43. data/features/support/test_server.rb +25 -4
  44. data/ftpd.gemspec +11 -3
  45. data/lib/ftpd.rb +2 -0
  46. data/lib/ftpd/auth_levels.rb +9 -0
  47. data/lib/ftpd/disk_file_system.rb +48 -19
  48. data/lib/ftpd/ftp_server.rb +13 -10
  49. data/lib/ftpd/read_only_disk_file_system.rb +22 -0
  50. data/lib/ftpd/session.rb +62 -29
  51. data/spec/disk_file_system_spec.rb +30 -0
  52. metadata +12 -4
data/lib/ftpd.rb CHANGED
@@ -20,6 +20,7 @@ module Ftpd
20
20
  autoload :FileSystemMethodMissing, 'ftpd/file_system_method_missing'
21
21
  autoload :FtpServer, 'ftpd/ftp_server'
22
22
  autoload :InsecureCertificate, 'ftpd/insecure_certificate'
23
+ autoload :ReadOnlyDiskFileSystem, 'ftpd/read_only_disk_file_system'
23
24
  autoload :Server, 'ftpd/server'
24
25
  autoload :Session, 'ftpd/session'
25
26
  autoload :TempDir, 'ftpd/temp_dir'
@@ -27,4 +28,5 @@ module Ftpd
27
28
  autoload :TranslateExceptions, 'ftpd/translate_exceptions'
28
29
  end
29
30
 
31
+ require 'ftpd/auth_levels'
30
32
  require 'ftpd/exceptions'
@@ -0,0 +1,9 @@
1
+ module Ftpd
2
+
3
+ # Authorization levels for FtpServer#auth_level
4
+
5
+ AUTH_USER = 0
6
+ AUTH_PASSWORD = 1
7
+ AUTH_ACCOUNT = 2
8
+
9
+ end
@@ -40,14 +40,6 @@ module Ftpd
40
40
  # directory named by the path may not exist.
41
41
  # @param ftp_path [String] The virtual path
42
42
  # @return [Boolean]
43
- #
44
- # Called for:
45
- # * STOR
46
- # * RETR
47
- # * DELE
48
- # * CWD
49
- # * MKD
50
- # * RMD
51
43
 
52
44
  def accessible?(ftp_path)
53
45
  # The server should never try to access a path outside of the
@@ -59,13 +51,6 @@ module Ftpd
59
51
  # Return true if the file or directory path exists.
60
52
  # @param ftp_path [String] The virtual path
61
53
  # @return [Boolean]
62
- #
63
- # Called for:
64
- # * STOR (with directory)
65
- # * RETR
66
- # * DELE
67
- # * CWD
68
- # * MKD
69
54
 
70
55
  def exists?(ftp_path)
71
56
  File.exists?(expand_ftp_path(ftp_path))
@@ -74,10 +59,6 @@ module Ftpd
74
59
  # Return true if the path exists and is a directory.
75
60
  # @param ftp_path [String] The virtual path
76
61
  # @return [Boolean]
77
- #
78
- # Called for:
79
- # * CWD
80
- # * MKD
81
62
 
82
63
  def directory?(ftp_path)
83
64
  File.directory?(expand_ftp_path(ftp_path))
@@ -162,6 +143,33 @@ module Ftpd
162
143
  end
163
144
  end
164
145
 
146
+ class DiskFileSystem
147
+
148
+ # DiskFileSystem mixin providing file appending
149
+
150
+ module Append
151
+
152
+ include TranslateExceptions
153
+
154
+ # Append to a file. If the file does not exist, create it.
155
+ # @param ftp_path [String] The virtual path
156
+ # @contents [String] The file's contents
157
+ #
158
+ # Called for:
159
+ # * APPE
160
+ #
161
+ # If missing, then these commands are not supported.
162
+
163
+ def append(ftp_path, contents)
164
+ File.open(expand_ftp_path(ftp_path), 'ab') do |file|
165
+ file.write contents
166
+ end
167
+ end
168
+ translate_exceptions :append
169
+
170
+ end
171
+ end
172
+
165
173
  class DiskFileSystem
166
174
 
167
175
  # DiskFileSystem mixing providing mkdir
@@ -360,6 +368,7 @@ module Ftpd
360
368
  # can be safely left out with the only effect being to make One or
361
369
  # more commands be unimplemented.
362
370
 
371
+ include DiskFileSystem::Append
363
372
  include DiskFileSystem::Delete
364
373
  include DiskFileSystem::List
365
374
  include DiskFileSystem::Mkdir
@@ -377,4 +386,24 @@ module Ftpd
377
386
  end
378
387
 
379
388
  end
389
+
390
+ # A disk file system that does not allow any modification (writes,
391
+ # deletes, etc.)
392
+
393
+ class ReadOnlyDiskFileSystem
394
+
395
+ include DiskFileSystem::Base
396
+ include DiskFileSystem::List
397
+ include DiskFileSystem::Read
398
+
399
+ # Make a new instance to serve a directory. data_dir should be an
400
+ # absolute path.
401
+
402
+ def initialize(data_dir)
403
+ set_data_dir data_dir
404
+ translate_exception SystemCallError
405
+ end
406
+
407
+ end
408
+
380
409
  end
@@ -31,6 +31,14 @@ module Ftpd
31
31
 
32
32
  attr_accessor :list_formatter
33
33
 
34
+ # @return [Integer] The authentication level
35
+ # One of:
36
+ # * Ftpd::AUTH_USER
37
+ # * Ftpd::AUTH_PASSWORD (default)
38
+ # * Ftpd::AUTH_ACCOUNT
39
+
40
+ attr_accessor :auth_level
41
+
34
42
  # Create a new FTP server. The server won't start until the
35
43
  # #start method is called.
36
44
  #
@@ -38,15 +46,8 @@ module Ftpd
38
46
  # authentication and file system access.
39
47
  #
40
48
  # The driver should expose these public methods:
41
- #
42
- # # Return truthy if the user/password should be allowed to
43
- # # log in.
44
- # authenticate(user, password)
45
- #
46
- # # Return the file system to use for a user. The file system
47
- # # should expose the same public methods as
48
- # # Ftpd::DiskFileSystem.
49
- # def file_system(user)
49
+ # * {Example::Driver#authenticate authenticate}
50
+ # * {Example::Driver#file_system file_system}
50
51
 
51
52
  def initialize(driver)
52
53
  super()
@@ -55,6 +56,7 @@ module Ftpd
55
56
  @debug = false
56
57
  @response_delay = 0
57
58
  @list_formatter = ListFormat::Ls
59
+ @auth_level = AUTH_PASSWORD
58
60
  end
59
61
 
60
62
  private
@@ -66,7 +68,8 @@ module Ftpd
66
68
  :debug_path => debug_path,
67
69
  :list_formatter => @list_formatter,
68
70
  :response_delay => response_delay,
69
- :tls => @tls).run
71
+ :tls => @tls,
72
+ :auth_level => @auth_level).run
70
73
  end
71
74
 
72
75
  end
@@ -0,0 +1,22 @@
1
+ module Ftpd
2
+
3
+ # A disk file system that does not allow any modification (writes,
4
+ # deletes, etc.)
5
+
6
+ class ReadOnlyDiskFileSystem
7
+
8
+ include DiskFileSystem::Base
9
+ include DiskFileSystem::List
10
+ include DiskFileSystem::Read
11
+
12
+ # Make a new instance to serve a directory. data_dir should be an
13
+ # absolute path.
14
+
15
+ def initialize(data_dir)
16
+ set_data_dir data_dir
17
+ translate_exception SystemCallError
18
+ end
19
+
20
+ end
21
+
22
+ end
data/lib/ftpd/session.rb CHANGED
@@ -7,6 +7,7 @@ module Ftpd
7
7
 
8
8
  def initialize(opts)
9
9
  @driver = opts[:driver]
10
+ @auth_level = opts[:auth_level]
10
11
  @socket = opts[:socket]
11
12
  @tls = opts[:tls]
12
13
  if @tls == :implicit
@@ -18,7 +19,6 @@ module Ftpd
18
19
  @list_formatter = opts[:list_formatter]
19
20
  @data_type = 'A'
20
21
  @mode = 'S'
21
- @format = 'N'
22
22
  @structure = 'F'
23
23
  @response_delay = opts[:response_delay]
24
24
  @data_channel_protection_level = :clear
@@ -65,19 +65,29 @@ module Ftpd
65
65
  syntax_error unless argument
66
66
  sequence_error if @logged_in
67
67
  @user = argument
68
- reply "331 Password required"
69
- expect 'pass'
68
+ if @auth_level > AUTH_USER
69
+ reply "331 Password required"
70
+ expect 'pass'
71
+ else
72
+ login(@user)
73
+ end
70
74
  end
71
75
 
72
76
  def cmd_pass(argument)
73
77
  syntax_error unless argument
74
- password = argument
75
- unless @driver.authenticate(@user, password)
76
- error "530 Login incorrect"
78
+ @password = argument
79
+ if @auth_level > AUTH_PASSWORD
80
+ reply "332 Account required"
81
+ expect 'acct'
82
+ else
83
+ login(@user, @password)
77
84
  end
78
- reply "230 Logged in"
79
- set_file_system @driver.file_system(@user)
80
- @logged_in = true
85
+ end
86
+
87
+ def cmd_acct(argument)
88
+ syntax_error unless argument
89
+ account = argument
90
+ login(@user, @password, account)
81
91
  end
82
92
 
83
93
  def cmd_quit(argument)
@@ -136,6 +146,20 @@ module Ftpd
136
146
  end
137
147
  end
138
148
 
149
+ def cmd_appe(argument)
150
+ close_data_server_socket_when_done do
151
+ ensure_logged_in
152
+ ensure_file_system_supports :append
153
+ path = argument
154
+ syntax_error unless path
155
+ path = File.expand_path(path, @name_prefix)
156
+ ensure_accessible path
157
+ contents = receive_file
158
+ @file_system.append path, contents
159
+ reply "226 Transfer complete"
160
+ end
161
+ end
162
+
139
163
  def cmd_retr(argument)
140
164
  close_data_server_socket_when_done do
141
165
  ensure_logged_in
@@ -190,26 +214,20 @@ module Ftpd
190
214
  syntax_error unless argument =~ /^(\S)(?: (\S+))?$/
191
215
  type_code = $1
192
216
  format_code = $2
193
- set_type(type_code)
194
- set_format(format_code)
217
+ unless argument =~ /^([AEI]( [NTC])?|L .*)$/
218
+ error '504 Invalid type code'
219
+ end
220
+ case argument
221
+ when /^A( [NT])?$/
222
+ @data_type = 'A'
223
+ when /^(I|L 8)$/
224
+ @data_type = 'I'
225
+ else
226
+ error '504 Type not implemented'
227
+ end
195
228
  reply "200 Type set to #{@data_type}"
196
229
  end
197
230
 
198
- def set_type(type_code)
199
- name, implemented = DATA_TYPES[type_code]
200
- error "504 Invalid type code" unless name
201
- error "504 Type not implemented" unless implemented
202
- @data_type = type_code
203
- end
204
-
205
- def set_format(format_code)
206
- format_code ||= 'N'
207
- name, implemented = FORMAT_TYPES[format_code]
208
- error "504 Invalid format code" unless name
209
- error "504 Format not implemented" unless implemented
210
- @data_format = format_code
211
- end
212
-
213
231
  def cmd_mode(argument)
214
232
  syntax_error unless argument
215
233
  ensure_logged_in
@@ -443,8 +461,6 @@ module Ftpd
443
461
  end
444
462
 
445
463
  unimplemented :abor
446
- unimplemented :acct
447
- unimplemented :appe
448
464
  unimplemented :rein
449
465
  unimplemented :rest
450
466
  unimplemented :site
@@ -469,7 +485,7 @@ module Ftpd
469
485
 
470
486
  FORMAT_TYPES = {
471
487
  'N'=>['Non-print', true],
472
- 'T'=>['Telnet format effectors', false],
488
+ 'T'=>['Telnet format effectors', true],
473
489
  'C'=>['Carriage Control (ASA)', false],
474
490
  }
475
491
 
@@ -674,6 +690,7 @@ module Ftpd
674
690
 
675
691
  def init_command_sequence_checker
676
692
  checker = CommandSequenceChecker.new
693
+ checker.must_expect 'acct'
677
694
  checker.must_expect 'pass'
678
695
  checker.must_expect 'rnto'
679
696
  checker
@@ -703,5 +720,21 @@ module Ftpd
703
720
  @file_system.dir(path).sort
704
721
  end
705
722
 
723
+ def authenticate(*args)
724
+ while args.size < @driver.method(:authenticate).arity
725
+ args << nil
726
+ end
727
+ @driver.authenticate(*args)
728
+ end
729
+
730
+ def login(*auth_tokens)
731
+ unless authenticate(*auth_tokens)
732
+ error "530 Login incorrect"
733
+ end
734
+ reply "230 Logged in"
735
+ set_file_system @driver.file_system(@user)
736
+ @logged_in = true
737
+ end
738
+
706
739
  end
707
740
  end
@@ -164,6 +164,36 @@ module Ftpd
164
164
 
165
165
  end
166
166
 
167
+ describe '#append' do
168
+
169
+ let(:contents) {'file contents'}
170
+
171
+ context '(destination missing)' do
172
+ let(:path) {'file_path'}
173
+ specify do
174
+ disk_file_system.append(path, contents)
175
+ read_file(path).should == contents
176
+ end
177
+ end
178
+
179
+ context '(destination exists)' do
180
+ let(:path) {'file'}
181
+ specify do
182
+ disk_file_system.append(path, contents)
183
+ read_file(path).should == canned_contents(path) + contents
184
+ end
185
+ end
186
+
187
+ context '(error)' do
188
+ specify do
189
+ expect {
190
+ disk_file_system.append('dir', contents)
191
+ }.to raise_error *is_a_directory_error
192
+ end
193
+ end
194
+
195
+ end
196
+
167
197
  describe '#mkdir' do
168
198
 
169
199
  context '(success)' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ftpd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-03-05 00:00:00.000000000 Z
12
+ date: 2013-03-06 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: memoizer
@@ -179,8 +179,10 @@ files:
179
179
  - examples/hello_world.rb
180
180
  - features/example/eplf.feature
181
181
  - features/example/example.feature
182
+ - features/example/read_only.feature
182
183
  - features/example/step_definitions/example_server.rb
183
184
  - features/ftp_server/allo.feature
185
+ - features/ftp_server/append.feature
184
186
  - features/ftp_server/cdup.feature
185
187
  - features/ftp_server/command_errors.feature
186
188
  - features/ftp_server/concurrent_sessions.feature
@@ -192,9 +194,12 @@ files:
192
194
  - features/ftp_server/get_tls.feature
193
195
  - features/ftp_server/help.feature
194
196
  - features/ftp_server/implicit_tls.feature
197
+ - features/ftp_server/invertability.feature
195
198
  - features/ftp_server/list.feature
196
199
  - features/ftp_server/list_tls.feature
197
- - features/ftp_server/login.feature
200
+ - features/ftp_server/login_auth_level_account.feature
201
+ - features/ftp_server/login_auth_level_password.feature
202
+ - features/ftp_server/login_auth_level_user.feature
198
203
  - features/ftp_server/mkdir.feature
199
204
  - features/ftp_server/mode.feature
200
205
  - features/ftp_server/name_list.feature
@@ -212,6 +217,7 @@ files:
212
217
  - features/ftp_server/syntax_errors.feature
213
218
  - features/ftp_server/syst.feature
214
219
  - features/ftp_server/type.feature
220
+ - features/step_definitions/append.rb
215
221
  - features/step_definitions/client.rb
216
222
  - features/step_definitions/client_and_server_files.rb
217
223
  - features/step_definitions/client_files.rb
@@ -255,6 +261,7 @@ files:
255
261
  - ftpd.gemspec
256
262
  - insecure-test-cert.pem
257
263
  - lib/ftpd.rb
264
+ - lib/ftpd/auth_levels.rb
258
265
  - lib/ftpd/command_sequence_checker.rb
259
266
  - lib/ftpd/disk_file_system.rb
260
267
  - lib/ftpd/error.rb
@@ -266,6 +273,7 @@ files:
266
273
  - lib/ftpd/insecure_certificate.rb
267
274
  - lib/ftpd/list_format/eplf.rb
268
275
  - lib/ftpd/list_format/ls.rb
276
+ - lib/ftpd/read_only_disk_file_system.rb
269
277
  - lib/ftpd/server.rb
270
278
  - lib/ftpd/session.rb
271
279
  - lib/ftpd/temp_dir.rb
@@ -301,7 +309,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
301
309
  version: '0'
302
310
  segments:
303
311
  - 0
304
- hash: 286911087
312
+ hash: 451937393
305
313
  required_rubygems_version: !ruby/object:Gem::Requirement
306
314
  none: false
307
315
  requirements: