ftpd 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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/.yardopts CHANGED
@@ -1,6 +1,7 @@
1
1
  -o doc-api
2
2
  --exclude '[~#]$'
3
3
  --readme README.md
4
+ --files Changelog.md
4
5
  --files doc/*.md
5
6
  lib/**/*.rb
6
7
  examples/example.rb
data/Changelog.md CHANGED
@@ -1,8 +1,32 @@
1
- ### 0.3.2
1
+ ### 0.4.0
2
2
 
3
3
  Enhancements
4
4
 
5
5
  * Improved driver and file-system documentation.
6
+ * Added {Ftpd::ReadOnlyDiskFileSystem read only disk file system}
7
+ * Example can be run with a read-only file system
8
+ * Supports three different levels of authentication:
9
+ * User
10
+ * User + Password
11
+ * User + Password + Account
12
+ * Added --auth switch to the example to select the authentication
13
+ level.
14
+ * Support APPE
15
+ * Support TYPE "A T" (ASCII/Telnet)
16
+ * Support TYPE "LOCAL 8"
17
+ * Added switches to example to set authentication values
18
+ * --user
19
+ * --password
20
+ * --account
21
+
22
+ API changes
23
+
24
+ * {Example::Driver#authenticate authenticate} now takes three
25
+ parameters (user, password, account). For compatability, it can be
26
+ defined to take only two, provided you are not doing account
27
+ authentication.
28
+ * Added {Ftpd::FtpServer#auth_level} option
29
+ * Added {Ftpd::DiskFileSystem::Append}
6
30
 
7
31
  ### 0.3.1
8
32
 
data/README.md CHANGED
@@ -8,7 +8,8 @@ embedded in a program.
8
8
  ## A note about this README
9
9
 
10
10
  This readme, and the other files, contains Yardoc markup, especially
11
- for links to the API docs. You'll find a properly rendered version
11
+ for links to the API docs; those links don't display properly on
12
+ github. You'll find a properly rendered version
12
13
  {http://rubydoc.info/gems/ftpd on rubydoc.info}
13
14
 
14
15
  ## HELLO WORLD
@@ -91,6 +92,77 @@ Here are the methods a file system may expose:
91
92
  * {Ftpd::DiskFileSystem::List#dir dir}
92
93
  * {Ftpd::DiskFileSystem::Rename#rename rename}
93
94
 
95
+ ### DiskFileSystem
96
+
97
+ Ftpd includes a disk based file system:
98
+
99
+ class Driver
100
+
101
+ ...
102
+
103
+ def file_system(user)
104
+ Ftpd::DiskFileSystem.new('/var/lib/ftp')
105
+ end
106
+
107
+ end
108
+
109
+ *Warning*: The DiskFileSystem allows file and directory modification
110
+ including writing, renaming, deleting, etc. If you want a read-only
111
+ file system, then use {Ftpd::ReadOnlyDiskFileSystem} instead.
112
+
113
+ The DiskFileSystem is composed out of modules:
114
+
115
+ * {Ftpd::DiskFileSystem::Base Base} - You will need this
116
+ * {Ftpd::DiskFileSystem::Delete Delete} - File deletion
117
+ * {Ftpd::DiskFileSystem::List List} - Directory listing
118
+ * {Ftpd::DiskFileSystem::Mkdir Mkdir} - Directory creation
119
+ * {Ftpd::DiskFileSystem::Read Read} - File reading
120
+ * {Ftpd::DiskFileSystem::Rename Rename} - File renaming
121
+ * {Ftpd::DiskFileSystem::Rmdir Rmdir} - Directory removal
122
+ * {Ftpd::DiskFileSystem::Write Write} - File writing
123
+
124
+ For example, to create a custom file system that allows reading and
125
+ writing only, then:
126
+
127
+ class CustomDiskFileSystem
128
+ include DiskFileSystem::Base
129
+ include DiskFileSystem::List
130
+ include DiskFileSystem::Read
131
+ include DiskFileSystem::Write
132
+ end
133
+
134
+ class Driver
135
+
136
+ ...
137
+
138
+ def file_system(user)
139
+ CustomDiskFileSystem('/var/lib/ftp')
140
+ end
141
+
142
+ end
143
+
144
+ ## LIST output format
145
+
146
+ By default, the LIST command uses Unix "ls -l" formatting:
147
+
148
+ -rw-r--r-- 1 user group 1234 Mar 3 08:38 foo
149
+
150
+ To switch to
151
+ {http://cr.yp.to/ftp/list/eplf.html Easily Parsed LIST format (EPLF)}
152
+ format:
153
+
154
+ ftp_server.list_formatter = Ftpd::ListFormat::Eplf
155
+
156
+ To create your own custom formatter, create a class with these
157
+ methods:
158
+
159
+ * {Ftpd::ListFormat::Ls#initialize initialize}
160
+ * {Ftpd::ListFormat::Ls#to_s to_s}
161
+
162
+ And register your class with the ftp_server before starting it:
163
+
164
+ ftp_server.list_formatter = MyListFormatter
165
+
94
166
  ## DEBUGGING
95
167
 
96
168
  Ftpd can write debugging information (essentially a transcript of its
@@ -111,16 +183,25 @@ output without having to change any code.
111
183
 
112
184
  ## LIMITATIONS
113
185
 
114
- Ftpd is not yet RFC compliant. It does most of RFC969, and enough TLS
115
- to get by. {file:doc/rfc.md Here} is a list of RFCs, indicating how
116
- much of each Ftpd complies with.
186
+ Ftpd is not fully RFC compliant. It does most of RFC969, and enough
187
+ TLS to get by. {file:doc/rfc.md Here} is a list of RFCs, indicating
188
+ how much of each Ftpd complies with.
189
+
190
+ RFC does not meet the following
191
+ [RFC-1123](http://tools.ietf.org/rfc/rfc1123.txt) "MUST" requrements.
192
+ If FTPD met these requirements, but did not meet the "SHOULD"
193
+ requirements, it would be "conditionally compliant":
194
+
195
+ * Server-FTP handle Telnet options
196
+ * Support command STAT
197
+
198
+ RFC does not meet the following
199
+ [RFC-1123](http://tools.ietf.org/rfc/rfc1123.txt)
200
+ "SHOULD" requrements. If FTPD met both the "MUST" and the "SHOULD"
201
+ requirements, it would be "unconditionally compliant":
117
202
 
118
- To bind the server to an external interface, the interface must be set
119
- to the public IP of that interface (e.g. "1.2.3.4"), not to "0.0.0.0".
120
- That's because the interface IP is used both for binding server ports,
121
- _and_ for advertising to the client which IP to connect to. Binding
122
- to 0.0.0.0 will work fine, but when the client tries to connect to
123
- 0.0.0.0, it won't get to the server.
203
+ * Idle timeout in server-FTP
204
+ * Configurable idle timeout
124
205
 
125
206
  ## RUBY COMPATABILITY
126
207
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.2
1
+ 0.4.0
@@ -22,10 +22,10 @@ pyftpdlib is what every FTP library wants to be when it grows up.
22
22
  Commands supported:
23
23
 
24
24
  ABOR No --- Abort transfer
25
- ACCT No --- Specify user's account
25
+ ACCT Yes 0.4.0 Specify user's account
26
26
  ALLO Yes 0.2.0 Allocate storage space
27
27
  Treated as a NOOP
28
- APPE No --- Append to file
28
+ APPE Yes 0.4.0 Append to file
29
29
  CDUP Yes 0.1.0 Change to parent directory
30
30
  CWD Yes 0.1.0 Change working directory
31
31
  DELE Yes 0.1.0 Delete file
@@ -87,18 +87,18 @@ with "C" where FTPD complies, or "E" where compliance is not required.
87
87
  | |T|D|Y|O|O|t
88
88
  FEATURE |SECTION | | | |T|T|e
89
89
  -------------------------------------------|---------------|-|-|-|-|-|--
90
- Implement TYPE T if same as TYPE N |4.1.2.2 | |x| | | |
91
- File/Record transform invertible if poss. |4.1.2.4 | |x| | | |
90
+ Implement TYPE T if same as TYPE N |4.1.2.2 | |x| | | | C
91
+ File/Record transform invertible if poss. |4.1.2.4 | |x| | | | C
92
92
  Server-FTP implement PASV |4.1.2.6 |x| | | | | C
93
93
  PASV is per-transfer |4.1.2.6 |x| | | | | C
94
94
  NLST reply usable in RETR cmds |4.1.2.7 |x| | | | | C
95
95
  Implied type for LIST and NLST |4.1.2.7 | |x| | | | C
96
- SITE cmd for non-standard features |4.1.2.8 | |x| | | |
96
+ SITE cmd for non-standard features |4.1.2.8 | |x| | | | C
97
97
  STOU cmd return pathname as specified |4.1.2.9 |x| | | | | C
98
98
  Use TCP READ boundaries on control conn. |4.1.2.10 | | | | |x| C
99
99
  Server-FTP send only correct reply format |4.1.2.11 |x| | | | | C
100
100
  Server-FTP use defined reply code if poss. |4.1.2.11 | |x| | | | C
101
- New reply code following Section 4.2 |4.1.2.11 | | |x| | |
101
+ New reply code following Section 4.2 |4.1.2.11 | | |x| | | E
102
102
  Default data port same IP addr as ctl conn |4.1.2.12 |x| | | | | C
103
103
  Server-FTP handle Telnet options |4.1.2.12 |x| | | | |
104
104
  Handle "Experimental" directory cmds |4.1.3.1 | |x| | | | C
@@ -109,30 +109,30 @@ Sender assume 110 replies are synchronous |4.1.3.4 | | | | |x|
109
109
  | | | | | | |
110
110
  Support TYPE: | | | | | | |
111
111
  ASCII - Non-Print (AN) |4.1.2.13 |x| | | | | C
112
- ASCII - Telnet (AT) -- if same as AN |4.1.2.2 | |x| | | |
113
- ASCII - Carriage Control (AC) |959 3.1.1.5.2 | | |x| | |
114
- EBCDIC - (any form) |959 3.1.1.2 | | |x| | |
112
+ ASCII - Telnet (AT) -- if same as AN |4.1.2.2 | |x| | | | C
113
+ ASCII - Carriage Control (AC) |959 3.1.1.5.2 | | |x| | | E
114
+ EBCDIC - (any form) |959 3.1.1.2 | | |x| | | E
115
115
  IMAGE |4.1.2.1 |x| | | | | C
116
- LOCAL 8 |4.1.2.1 |x| | | | |
117
- LOCAL m |4.1.2.1 | | |x| | |2
116
+ LOCAL 8 |4.1.2.1 |x| | | | | C
117
+ LOCAL m |4.1.2.1 | | |x| | |2 E
118
118
  | | | | | | |
119
119
  Support MODE: | | | | | | |
120
120
  Stream |4.1.2.13 |x| | | | | C
121
- Block |959 3.4.2 | | |x| | |
121
+ Block |959 3.4.2 | | |x| | | E
122
122
  | | | | | | |
123
123
  Support STRUCTURE: | | | | | | |
124
124
  File |4.1.2.13 |x| | | | | C
125
125
  Record |4.1.2.13 |x| | | | |3 E
126
- Page |4.1.2.3 | | | |x| |
126
+ Page |4.1.2.3 | | | |x| | E
127
127
  | | | | | | |
128
128
  Support commands: | | | | | | |
129
129
  USER |4.1.2.13 |x| | | | | C
130
130
  PASS |4.1.2.13 |x| | | | | C
131
- ACCT |4.1.2.13 |x| | | | |
131
+ ACCT |4.1.2.13 |x| | | | | C
132
132
  CWD |4.1.2.13 |x| | | | | C
133
133
  CDUP |4.1.2.13 |x| | | | | C
134
- SMNT |959 5.3.1 | | |x| | |
135
- REIN |959 5.3.1 | | |x| | |
134
+ SMNT |959 5.3.1 | | |x| | | E
135
+ REIN |959 5.3.1 | | |x| | | E
136
136
  QUIT |4.1.2.13 |x| | | | | C
137
137
  | | | | | | |
138
138
  PORT |4.1.2.13 |x| | | | | C
@@ -144,19 +144,19 @@ Support commands: | | | | | | |
144
144
  RETR |4.1.2.13 |x| | | | | C
145
145
  STOR |4.1.2.13 |x| | | | | C
146
146
  STOU |959 5.3.1 | | |x| | | C
147
- APPE |4.1.2.13 |x| | | | |
147
+ APPE |4.1.2.13 |x| | | | | C
148
148
  ALLO |959 5.3.1 | | |x| | | C
149
- REST |959 5.3.1 | | |x| | |
149
+ REST |959 5.3.1 | | |x| | | E
150
150
  RNFR |4.1.2.13 |x| | | | | C
151
151
  RNTO |4.1.2.13 |x| | | | | C
152
- ABOR |959 5.3.1 | | |x| | |
152
+ ABOR |959 5.3.1 | | |x| | | E
153
153
  DELE |4.1.2.13 |x| | | | | C
154
154
  RMD |4.1.2.13 |x| | | | | C
155
155
  MKD |4.1.2.13 |x| | | | | C
156
156
  PWD |4.1.2.13 |x| | | | | C
157
157
  LIST |4.1.2.13 |x| | | | | C
158
158
  NLST |4.1.2.13 |x| | | | | C
159
- SITE |4.1.2.8 | | |x| | |
159
+ SITE |4.1.2.8 | | |x| | | E
160
160
  STAT |4.1.2.13 |x| | | | |
161
161
  SYST |4.1.2.13 |x| | | | | C
162
162
  HELP |4.1.2.13 |x| | | | | C
data/examples/example.rb CHANGED
@@ -13,15 +13,24 @@ module Example
13
13
 
14
14
  class Arguments
15
15
 
16
+ attr_reader :account
17
+ attr_reader :auth_level
16
18
  attr_reader :eplf
17
19
  attr_reader :interface
20
+ attr_reader :password
18
21
  attr_reader :port
22
+ attr_reader :read_only
19
23
  attr_reader :tls
24
+ attr_reader :user
20
25
 
21
26
  def initialize(argv)
22
27
  @interface = 'localhost'
23
28
  @tls = :explicit
24
29
  @port = 0
30
+ @auth_level = 'password'
31
+ @user = ENV['LOGNAME']
32
+ @password = ''
33
+ @account = ''
25
34
  op = option_parser
26
35
  op.parse!(argv)
27
36
  rescue OptionParser::ParseError => e
@@ -40,11 +49,32 @@ module Example
40
49
  @interface = t
41
50
  end
42
51
  op.on('--tls [TYPE]', [:off, :explicit, :implicit],
43
- 'Select TLS support (off, explicit, implicit)') do |t|
52
+ 'Select TLS support (off, explicit, implicit)',
53
+ 'default = off') do |t|
44
54
  @tls = t
45
55
  end
46
56
  op.on('--eplf', 'LIST uses EPLF format') do |t|
47
57
  @eplf = t
58
+ end
59
+ op.on('--read-only', 'Prohibit put, delete, rmdir, etc.') do |t|
60
+ @read_only = t
61
+ end
62
+ op.on('--auth [LEVEL]', [:user, :password, :account],
63
+ 'Set authorization level (user, password, account)',
64
+ 'default = password') do |t|
65
+ @auth_level = t
66
+ end
67
+ op.on('-U', '--user NAME', 'User for authentication',
68
+ 'defaults to current user') do |t|
69
+ @user = t
70
+ end
71
+ op.on('-P', '--password PW', 'Password for authentication',
72
+ 'defaults to empty string') do |t|
73
+ @password = t
74
+ end
75
+ op.on('-A', '--account PW', 'Account for authentication',
76
+ 'defaults to empty string') do |t|
77
+ @account = t
48
78
  end
49
79
  end
50
80
  end
@@ -64,19 +94,32 @@ module Example
64
94
  # Your driver's initialize method can be anything you need. Ftpd
65
95
  # does not create an instance of your driver.
66
96
 
67
- def initialize(user, password, data_dir)
97
+ def initialize(user, password, account, data_dir, read_only)
68
98
  @user = user
69
99
  @password = password
100
+ @account = account
70
101
  @data_dir = data_dir
102
+ @read_only = read_only
71
103
  end
72
104
 
73
105
  # Return true if the user should be allowed to log in.
74
106
  # @param user [String]
75
107
  # @param password [String]
108
+ # @param account [String]
76
109
  # @return [Boolean]
77
-
78
- def authenticate(user, password)
79
- user == @user && password == @password
110
+ #
111
+ # Depending upon the server's auth_level, some of these parameters
112
+ # may be nil. A parameter with a nil value is not required for
113
+ # authentication. Here are the parameters that are non-nil for
114
+ # each auth_level:
115
+ # * :user (user)
116
+ # * :password (user, password)
117
+ # * :account (user, password, account)
118
+
119
+ def authenticate(user, password, account)
120
+ user == @user &&
121
+ (password.nil? || password == @password) &&
122
+ (account.nil? || account == @account)
80
123
  end
81
124
 
82
125
  # Return the file system to use for a user.
@@ -84,7 +127,11 @@ module Example
84
127
  # @return A file system driver that quacks like {Ftpd::DiskFileSystem}
85
128
 
86
129
  def file_system(user)
87
- Ftpd::DiskFileSystem.new(@data_dir)
130
+ if @read_only
131
+ Ftpd::ReadOnlyDiskFileSystem
132
+ else
133
+ Ftpd::DiskFileSystem
134
+ end.new(@data_dir)
88
135
  end
89
136
 
90
137
  end
@@ -99,7 +146,8 @@ module Example
99
146
  @args = Arguments.new(argv)
100
147
  @data_dir = Ftpd::TempDir.make
101
148
  create_files
102
- @driver = Driver.new(user, password, @data_dir)
149
+ @driver = Driver.new(user, password, account,
150
+ @data_dir, @args.read_only)
103
151
  @server = Ftpd::FtpServer.new(@driver)
104
152
  @server.interface = @args.interface
105
153
  @server.port = @args.port
@@ -108,6 +156,7 @@ module Example
108
156
  if @args.eplf
109
157
  @server.list_formatter = Ftpd::ListFormat::Eplf
110
158
  end
159
+ @server.auth_level = auth_level
111
160
  @server.start
112
161
  display_connection_info
113
162
  create_connection_script
@@ -121,6 +170,10 @@ module Example
121
170
 
122
171
  HOST = 'localhost'
123
172
 
173
+ def auth_level
174
+ Ftpd.const_get("AUTH_#{@args.auth_level.upcase}")
175
+ end
176
+
124
177
  def create_files
125
178
  create_file 'README',
126
179
  "This file, and the directory it is in, will go away\n"
@@ -138,8 +191,9 @@ module Example
138
191
  def display_connection_info
139
192
  puts "Interface: #{@server.interface}"
140
193
  puts "Port: #{@server.bound_port}"
141
- puts "User: #{user}"
142
- puts "Pass: #{password}"
194
+ puts "User: #{user.inspect}"
195
+ puts "Pass: #{password.inspect}" if auth_level >= Ftpd::AUTH_PASSWORD
196
+ puts "Acctount: #{account.inspect}" if auth_level >= Ftpd::AUTH_ACCOUNT
143
197
  puts "TLS: #{@args.tls}"
144
198
  puts "Directory: #{@data_dir}"
145
199
  puts "URI: ftp://#{HOST}:#{@server.bound_port}"
@@ -166,11 +220,15 @@ module Example
166
220
  end
167
221
 
168
222
  def user
169
- ENV['LOGNAME']
223
+ @args.user
170
224
  end
171
225
 
172
226
  def password
173
- ''
227
+ @args.password
228
+ end
229
+
230
+ def account
231
+ @args.account
174
232
  end
175
233
 
176
234
  end
@@ -0,0 +1,63 @@
1
+ Feature: Example
2
+
3
+ As a programmer
4
+ I want to start a read-only server
5
+ So that nobody can modify the file system I expose
6
+
7
+ Background:
8
+ Given the example has argument "--read-only"
9
+ And the example server is started
10
+
11
+ Scenario: Fetch README
12
+ Given a successful login
13
+ When the client successfully gets text "README"
14
+ Then the local file "README" should match the remote file
15
+
16
+ Scenario: Fetch README
17
+ Given a successful login
18
+ When the client successfully gets text "README"
19
+ Then the local file "README" should match the remote file
20
+
21
+ Scenario: List
22
+ Given a successful login
23
+ When the client successfully lists the directory
24
+ Then the file list should be in long form
25
+ And the file list should contain "README"
26
+
27
+ Scenario: Name List
28
+ Given a successful login
29
+ When the client successfully name-lists the directory
30
+ Then the file list should be in short form
31
+ And the file list should contain "README"
32
+
33
+ Scenario: Put
34
+ Given a successful login
35
+ And the client has file "foo"
36
+ When the client puts text "foo"
37
+ Then the server returns an unimplemented command error
38
+
39
+ Scenario: Put unique
40
+ Given a successful login
41
+ And the client has file "foo"
42
+ When the client stores unique "foo"
43
+ Then the server returns an unimplemented command error
44
+
45
+ Scenario: Delete
46
+ Given a successful login
47
+ When the client deletes "README"
48
+ Then the server returns an unimplemented command error
49
+
50
+ Scenario: Mkdir
51
+ Given a successful login
52
+ When the client makes directory "foo"
53
+ Then the server returns an unimplemented command error
54
+
55
+ Scenario: Rename
56
+ Given a successful login
57
+ When the client renames "README" to "foo"
58
+ Then the server returns an unimplemented command error
59
+
60
+ Scenario: Rmdir
61
+ Given a successful login
62
+ When the client removes directory "foo"
63
+ Then the server returns an unimplemented command error