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/.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