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.
- data/.yardopts +1 -0
- data/Changelog.md +25 -1
- data/README.md +91 -10
- data/VERSION +1 -1
- data/doc/rfc-compliance.md +20 -20
- data/examples/example.rb +69 -11
- data/features/example/read_only.feature +63 -0
- data/features/ftp_server/append.feature +94 -0
- data/features/ftp_server/command_errors.feature +0 -2
- data/features/ftp_server/concurrent_sessions.feature +1 -1
- data/features/ftp_server/debug.feature +4 -2
- data/features/ftp_server/delete.feature +1 -1
- data/features/ftp_server/get.feature +1 -1
- data/features/ftp_server/get_tls.feature +2 -1
- data/features/ftp_server/implicit_tls.feature +2 -1
- data/features/ftp_server/invertability.feature +15 -0
- data/features/ftp_server/list.feature +1 -1
- data/features/ftp_server/list_tls.feature +2 -1
- data/features/ftp_server/login_auth_level_account.feature +51 -0
- data/features/ftp_server/{login.feature → login_auth_level_password.feature} +4 -8
- data/features/ftp_server/login_auth_level_user.feature +31 -0
- data/features/ftp_server/mkdir.feature +1 -1
- data/features/ftp_server/name_list.feature +1 -1
- data/features/ftp_server/name_list_tls.feature +2 -1
- data/features/ftp_server/put.feature +1 -1
- data/features/ftp_server/put_tls.feature +2 -1
- data/features/ftp_server/put_unique.feature +1 -1
- data/features/ftp_server/rename.feature +1 -1
- data/features/ftp_server/rmdir.feature +1 -1
- data/features/ftp_server/step_definitions/debug.rb +1 -1
- data/features/ftp_server/step_definitions/test_server.rb +16 -15
- data/features/ftp_server/type.feature +11 -8
- data/features/step_definitions/append.rb +15 -0
- data/features/step_definitions/client_and_server_files.rb +2 -2
- data/features/step_definitions/client_files.rb +5 -0
- data/features/step_definitions/connect.rb +1 -1
- data/features/step_definitions/error_replies.rb +0 -4
- data/features/step_definitions/login.rb +30 -20
- data/features/step_definitions/server_files.rb +20 -7
- data/features/support/example_server.rb +10 -2
- data/features/support/test_client.rb +18 -0
- data/features/support/test_file_templates.rb +1 -1
- data/features/support/test_server.rb +25 -4
- data/ftpd.gemspec +11 -3
- data/lib/ftpd.rb +2 -0
- data/lib/ftpd/auth_levels.rb +9 -0
- data/lib/ftpd/disk_file_system.rb +48 -19
- data/lib/ftpd/ftp_server.rb +13 -10
- data/lib/ftpd/read_only_disk_file_system.rb +22 -0
- data/lib/ftpd/session.rb +62 -29
- data/spec/disk_file_system_spec.rb +30 -0
- 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'
|
@@ -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
|
data/lib/ftpd/ftp_server.rb
CHANGED
@@ -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
|
-
#
|
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
|
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
|
-
|
69
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
194
|
-
|
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',
|
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.
|
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-
|
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/
|
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:
|
312
|
+
hash: 451937393
|
305
313
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
306
314
|
none: false
|
307
315
|
requirements:
|