investtools-ftpd 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (219) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +5 -0
  3. data/.yardopts +7 -0
  4. data/Changelog.md +310 -0
  5. data/Gemfile +15 -0
  6. data/Gemfile.lock +93 -0
  7. data/LICENSE.md +9 -0
  8. data/README.md +371 -0
  9. data/Rakefile +14 -0
  10. data/VERSION +1 -0
  11. data/doc/benchmarks.md +82 -0
  12. data/doc/references.md +66 -0
  13. data/doc/rfc-compliance.md +292 -0
  14. data/examples/example.rb +275 -0
  15. data/examples/example_spec.rb +93 -0
  16. data/examples/hello_world.rb +32 -0
  17. data/features/example/eplf.feature +14 -0
  18. data/features/example/example.feature +18 -0
  19. data/features/example/read_only.feature +63 -0
  20. data/features/example/step_definitions/example_server.rb +11 -0
  21. data/features/ftp_server/abort.feature +13 -0
  22. data/features/ftp_server/allo.feature +33 -0
  23. data/features/ftp_server/append.feature +94 -0
  24. data/features/ftp_server/cdup.feature +36 -0
  25. data/features/ftp_server/command_errors.feature +13 -0
  26. data/features/ftp_server/concurrent_sessions.feature +14 -0
  27. data/features/ftp_server/delay_after_failed_login.feature +23 -0
  28. data/features/ftp_server/delete.feature +60 -0
  29. data/features/ftp_server/directory_navigation.feature +59 -0
  30. data/features/ftp_server/disconnect_after_failed_logins.feature +25 -0
  31. data/features/ftp_server/eprt.feature +55 -0
  32. data/features/ftp_server/epsv.feature +36 -0
  33. data/features/ftp_server/features.feature +38 -0
  34. data/features/ftp_server/file_structure.feature +43 -0
  35. data/features/ftp_server/get.feature +80 -0
  36. data/features/ftp_server/get_ipv6.feature +43 -0
  37. data/features/ftp_server/get_tls.feature +23 -0
  38. data/features/ftp_server/help.feature +21 -0
  39. data/features/ftp_server/implicit_tls.feature +23 -0
  40. data/features/ftp_server/invertability.feature +15 -0
  41. data/features/ftp_server/list.feature +94 -0
  42. data/features/ftp_server/list_tls.feature +29 -0
  43. data/features/ftp_server/logging.feature +11 -0
  44. data/features/ftp_server/login_auth_level_account.feature +51 -0
  45. data/features/ftp_server/login_auth_level_password.feature +59 -0
  46. data/features/ftp_server/login_auth_level_user.feature +31 -0
  47. data/features/ftp_server/max_connections.feature +39 -0
  48. data/features/ftp_server/mdtm.feature +53 -0
  49. data/features/ftp_server/mkdir.feature +70 -0
  50. data/features/ftp_server/mode.feature +43 -0
  51. data/features/ftp_server/name_list.feature +77 -0
  52. data/features/ftp_server/name_list_tls.feature +30 -0
  53. data/features/ftp_server/noop.feature +17 -0
  54. data/features/ftp_server/options.feature +17 -0
  55. data/features/ftp_server/pasv.feature +23 -0
  56. data/features/ftp_server/port.feature +49 -0
  57. data/features/ftp_server/put.feature +79 -0
  58. data/features/ftp_server/put_tls.feature +23 -0
  59. data/features/ftp_server/put_unique.feature +56 -0
  60. data/features/ftp_server/quit.feature +23 -0
  61. data/features/ftp_server/reinitialize.feature +13 -0
  62. data/features/ftp_server/rename.feature +97 -0
  63. data/features/ftp_server/rmdir.feature +71 -0
  64. data/features/ftp_server/site.feature +13 -0
  65. data/features/ftp_server/size.feature +69 -0
  66. data/features/ftp_server/status.feature +18 -0
  67. data/features/ftp_server/step_definitions/logging.rb +8 -0
  68. data/features/ftp_server/step_definitions/test_server.rb +65 -0
  69. data/features/ftp_server/structure_mount.feature +13 -0
  70. data/features/ftp_server/syntax_errors.feature +18 -0
  71. data/features/ftp_server/syst.feature +18 -0
  72. data/features/ftp_server/timeout.feature +26 -0
  73. data/features/ftp_server/type.feature +59 -0
  74. data/features/step_definitions/append.rb +15 -0
  75. data/features/step_definitions/client.rb +24 -0
  76. data/features/step_definitions/client_and_server_files.rb +24 -0
  77. data/features/step_definitions/client_files.rb +14 -0
  78. data/features/step_definitions/command.rb +5 -0
  79. data/features/step_definitions/connect.rb +37 -0
  80. data/features/step_definitions/delete.rb +15 -0
  81. data/features/step_definitions/directory_navigation.rb +26 -0
  82. data/features/step_definitions/error_replies.rb +115 -0
  83. data/features/step_definitions/features.rb +21 -0
  84. data/features/step_definitions/file_structure.rb +16 -0
  85. data/features/step_definitions/generic_send.rb +9 -0
  86. data/features/step_definitions/get.rb +16 -0
  87. data/features/step_definitions/help.rb +18 -0
  88. data/features/step_definitions/invalid_commands.rb +11 -0
  89. data/features/step_definitions/line_endings.rb +7 -0
  90. data/features/step_definitions/list.rb +73 -0
  91. data/features/step_definitions/login.rb +82 -0
  92. data/features/step_definitions/mkdir.rb +9 -0
  93. data/features/step_definitions/mode.rb +15 -0
  94. data/features/step_definitions/mtime.rb +23 -0
  95. data/features/step_definitions/noop.rb +15 -0
  96. data/features/step_definitions/options.rb +9 -0
  97. data/features/step_definitions/passive.rb +3 -0
  98. data/features/step_definitions/pending.rb +3 -0
  99. data/features/step_definitions/port.rb +5 -0
  100. data/features/step_definitions/put.rb +29 -0
  101. data/features/step_definitions/quit.rb +15 -0
  102. data/features/step_definitions/rename.rb +11 -0
  103. data/features/step_definitions/rmdir.rb +9 -0
  104. data/features/step_definitions/server_files.rb +61 -0
  105. data/features/step_definitions/server_title.rb +12 -0
  106. data/features/step_definitions/size.rb +20 -0
  107. data/features/step_definitions/status.rb +9 -0
  108. data/features/step_definitions/success_replies.rb +7 -0
  109. data/features/step_definitions/system.rb +7 -0
  110. data/features/step_definitions/timing.rb +19 -0
  111. data/features/step_definitions/type.rb +15 -0
  112. data/features/support/env.rb +4 -0
  113. data/features/support/example_server.rb +67 -0
  114. data/features/support/file_templates/ascii_unix +4 -0
  115. data/features/support/file_templates/ascii_windows +4 -0
  116. data/features/support/file_templates/binary +0 -0
  117. data/features/support/test_client.rb +250 -0
  118. data/features/support/test_file_templates.rb +33 -0
  119. data/features/support/test_server.rb +293 -0
  120. data/features/support/test_server_files.rb +57 -0
  121. data/ftpd.gemspec +283 -0
  122. data/insecure-test-cert.pem +29 -0
  123. data/investtools-ftpd.gemspec +284 -0
  124. data/lib/ftpd.rb +86 -0
  125. data/lib/ftpd/auth_levels.rb +9 -0
  126. data/lib/ftpd/cmd_abor.rb +13 -0
  127. data/lib/ftpd/cmd_allo.rb +20 -0
  128. data/lib/ftpd/cmd_appe.rb +24 -0
  129. data/lib/ftpd/cmd_auth.rb +21 -0
  130. data/lib/ftpd/cmd_cdup.rb +16 -0
  131. data/lib/ftpd/cmd_cwd.rb +20 -0
  132. data/lib/ftpd/cmd_dele.rb +21 -0
  133. data/lib/ftpd/cmd_eprt.rb +23 -0
  134. data/lib/ftpd/cmd_epsv.rb +30 -0
  135. data/lib/ftpd/cmd_feat.rb +44 -0
  136. data/lib/ftpd/cmd_help.rb +29 -0
  137. data/lib/ftpd/cmd_list.rb +33 -0
  138. data/lib/ftpd/cmd_login.rb +60 -0
  139. data/lib/ftpd/cmd_mdtm.rb +27 -0
  140. data/lib/ftpd/cmd_mkd.rb +23 -0
  141. data/lib/ftpd/cmd_mode.rb +27 -0
  142. data/lib/ftpd/cmd_nlst.rb +27 -0
  143. data/lib/ftpd/cmd_noop.rb +14 -0
  144. data/lib/ftpd/cmd_opts.rb +14 -0
  145. data/lib/ftpd/cmd_pasv.rb +28 -0
  146. data/lib/ftpd/cmd_pbsz.rb +23 -0
  147. data/lib/ftpd/cmd_port.rb +28 -0
  148. data/lib/ftpd/cmd_prot.rb +34 -0
  149. data/lib/ftpd/cmd_pwd.rb +15 -0
  150. data/lib/ftpd/cmd_quit.rb +18 -0
  151. data/lib/ftpd/cmd_rein.rb +13 -0
  152. data/lib/ftpd/cmd_rename.rb +32 -0
  153. data/lib/ftpd/cmd_rest.rb +13 -0
  154. data/lib/ftpd/cmd_retr.rb +24 -0
  155. data/lib/ftpd/cmd_rmd.rb +22 -0
  156. data/lib/ftpd/cmd_site.rb +13 -0
  157. data/lib/ftpd/cmd_size.rb +29 -0
  158. data/lib/ftpd/cmd_smnt.rb +13 -0
  159. data/lib/ftpd/cmd_stat.rb +15 -0
  160. data/lib/ftpd/cmd_stor.rb +25 -0
  161. data/lib/ftpd/cmd_stou.rb +25 -0
  162. data/lib/ftpd/cmd_stru.rb +27 -0
  163. data/lib/ftpd/cmd_syst.rb +16 -0
  164. data/lib/ftpd/cmd_type.rb +28 -0
  165. data/lib/ftpd/command_handler.rb +90 -0
  166. data/lib/ftpd/command_handler_factory.rb +51 -0
  167. data/lib/ftpd/command_handlers.rb +60 -0
  168. data/lib/ftpd/command_loop.rb +80 -0
  169. data/lib/ftpd/command_sequence_checker.rb +58 -0
  170. data/lib/ftpd/config.rb +13 -0
  171. data/lib/ftpd/connection_throttle.rb +56 -0
  172. data/lib/ftpd/connection_tracker.rb +82 -0
  173. data/lib/ftpd/data_connection_helper.rb +123 -0
  174. data/lib/ftpd/disk_file_system.rb +434 -0
  175. data/lib/ftpd/error.rb +21 -0
  176. data/lib/ftpd/exception_translator.rb +32 -0
  177. data/lib/ftpd/exceptions.rb +62 -0
  178. data/lib/ftpd/file_info.rb +115 -0
  179. data/lib/ftpd/file_system_helper.rb +67 -0
  180. data/lib/ftpd/ftp_server.rb +214 -0
  181. data/lib/ftpd/gets_peer_address.rb +41 -0
  182. data/lib/ftpd/insecure_certificate.rb +16 -0
  183. data/lib/ftpd/list_format/eplf.rb +74 -0
  184. data/lib/ftpd/list_format/ls.rb +154 -0
  185. data/lib/ftpd/list_path.rb +28 -0
  186. data/lib/ftpd/null_logger.rb +22 -0
  187. data/lib/ftpd/protocols.rb +60 -0
  188. data/lib/ftpd/read_only_disk_file_system.rb +22 -0
  189. data/lib/ftpd/server.rb +139 -0
  190. data/lib/ftpd/session.rb +220 -0
  191. data/lib/ftpd/session_config.rb +111 -0
  192. data/lib/ftpd/stream.rb +80 -0
  193. data/lib/ftpd/telnet.rb +114 -0
  194. data/lib/ftpd/temp_dir.rb +22 -0
  195. data/lib/ftpd/tls_server.rb +111 -0
  196. data/lib/ftpd/translate_exceptions.rb +68 -0
  197. data/rake_tasks/cucumber.rake +9 -0
  198. data/rake_tasks/default.rake +1 -0
  199. data/rake_tasks/jeweler.rake +52 -0
  200. data/rake_tasks/spec.rake +3 -0
  201. data/rake_tasks/test.rake +2 -0
  202. data/rake_tasks/yard.rake +3 -0
  203. data/spec/command_sequence_checker_spec.rb +83 -0
  204. data/spec/connection_throttle_spec.rb +99 -0
  205. data/spec/connection_tracker_spec.rb +97 -0
  206. data/spec/disk_file_system_spec.rb +320 -0
  207. data/spec/exception_translator_spec.rb +36 -0
  208. data/spec/file_info_spec.rb +59 -0
  209. data/spec/ftp_server_error_spec.rb +13 -0
  210. data/spec/list_format/eplf_spec.rb +61 -0
  211. data/spec/list_format/ls_spec.rb +270 -0
  212. data/spec/list_path_spec.rb +21 -0
  213. data/spec/null_logger_spec.rb +24 -0
  214. data/spec/protocols_spec.rb +139 -0
  215. data/spec/server_spec.rb +81 -0
  216. data/spec/spec_helper.rb +15 -0
  217. data/spec/telnet_spec.rb +75 -0
  218. data/spec/translate_exceptions_spec.rb +40 -0
  219. metadata +404 -0
@@ -0,0 +1,220 @@
1
+ module Ftpd
2
+ class Session
3
+
4
+ include Error
5
+ include ListPath
6
+
7
+ attr_accessor :command_sequence_checker
8
+ attr_accessor :data_channel_protection_level
9
+ attr_accessor :data_server
10
+ attr_accessor :data_type
11
+ attr_accessor :logged_in
12
+ attr_accessor :name_prefix
13
+ attr_accessor :protection_buffer_size_set
14
+ attr_accessor :socket
15
+ attr_reader :config
16
+ attr_reader :data_hostname
17
+ attr_reader :data_port
18
+ attr_reader :file_system
19
+ attr_writer :epsv_all
20
+ attr_writer :mode
21
+ attr_writer :structure
22
+
23
+ # @params session_config [SessionConfig] Session configuration
24
+ # @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] The socket
25
+
26
+ def initialize(session_config, socket)
27
+ @config = session_config
28
+ @socket = socket
29
+ if @config.tls == :implicit
30
+ @socket.encrypt
31
+ end
32
+ @command_sequence_checker = init_command_sequence_checker
33
+ set_socket_options
34
+ @protocols = Protocols.new(@socket)
35
+ @command_handlers = CommandHandlers.new
36
+ @command_loop = CommandLoop.new(self)
37
+ register_commands
38
+ initialize_session
39
+ end
40
+
41
+ def run
42
+ @command_loop.read_and_execute_commands
43
+ end
44
+
45
+ private
46
+
47
+ def register_commands
48
+ handlers = CommandHandlerFactory.standard_command_handlers
49
+ handlers.each do |klass|
50
+ @command_handlers << klass.new(self)
51
+ end
52
+ end
53
+
54
+ def valid_command?(command)
55
+ @command_handlers.has?(command)
56
+ end
57
+
58
+ def execute_command command, argument
59
+ @command_handlers.execute command, argument
60
+ end
61
+
62
+ def ensure_logged_in
63
+ return if @logged_in
64
+ error "Not logged in", 530
65
+ end
66
+
67
+ def ensure_tls_supported
68
+ unless tls_enabled?
69
+ error "TLS not enabled", 534
70
+ end
71
+ end
72
+
73
+ def ensure_not_epsv_all
74
+ if @epsv_all
75
+ error "Not allowed after EPSV ALL", 501
76
+ end
77
+ end
78
+
79
+ def tls_enabled?
80
+ @config.tls != :off
81
+ end
82
+
83
+ def ensure_protocol_supported(protocol_code)
84
+ unless @protocols.supports_protocol?(protocol_code)
85
+ protocol_list = @protocols.protocol_codes.join(',')
86
+ error("Network protocol #{protocol_code} not supported, "\
87
+ "use (#{protocol_list})", 522)
88
+ end
89
+ end
90
+
91
+ def supported_commands
92
+ @command_handlers.commands.map(&:upcase)
93
+ end
94
+
95
+ def pwd(status_code)
96
+ reply %Q(#{status_code} "#{@name_prefix}" is current directory)
97
+ end
98
+
99
+ FORMAT_TYPES = {
100
+ 'N'=>['Non-print', true],
101
+ 'T'=>['Telnet format effectors', true],
102
+ 'C'=>['Carriage Control (ASA)', false],
103
+ }
104
+
105
+ DATA_TYPES = {
106
+ 'A'=>['ASCII', true],
107
+ 'E'=>['EBCDIC', false],
108
+ 'I'=>['BINARY', true],
109
+ 'L'=>['LOCAL', false],
110
+ }
111
+
112
+ def expect(command)
113
+ @command_sequence_checker.expect command
114
+ end
115
+
116
+ def set_file_system(file_system)
117
+ @file_system = file_system
118
+ end
119
+
120
+ def command_not_needed
121
+ reply '202 Command not needed at this site'
122
+ end
123
+
124
+ def close_data_server_socket
125
+ return unless @data_server
126
+ @data_server.close
127
+ @data_server = nil
128
+ end
129
+
130
+ def reply(s)
131
+ if @config.response_delay.to_i != 0
132
+ @config.log.warn "#{@config.response_delay} second delay before replying"
133
+ sleep @config.response_delay
134
+ end
135
+ @config.log.debug s
136
+ @socket.write s + "\r\n"
137
+ end
138
+
139
+ def init_command_sequence_checker
140
+ checker = CommandSequenceChecker.new
141
+ checker.must_expect 'acct'
142
+ checker.must_expect 'pass'
143
+ checker.must_expect 'rnto'
144
+ checker
145
+ end
146
+
147
+ def authenticate(*args)
148
+ while args.size < @config.driver.method(:authenticate).arity
149
+ args << nil
150
+ end
151
+ @config.driver.authenticate(*args)
152
+ end
153
+
154
+ def login(*auth_tokens)
155
+ user = auth_tokens.first
156
+ unless authenticate(*auth_tokens)
157
+ failed_auth
158
+ error "Login incorrect", 530
159
+ end
160
+ reply "230 Logged in"
161
+ set_file_system @config.driver.file_system(user)
162
+ @logged_in = true
163
+ reset_failed_auths
164
+ end
165
+
166
+ def set_socket_options
167
+ disable_nagle @socket
168
+ receive_oob_data_inline @socket
169
+ end
170
+
171
+ def disable_nagle(socket)
172
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
173
+ end
174
+
175
+ def receive_oob_data_inline(socket)
176
+ socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1)
177
+ end
178
+
179
+ def reset_failed_auths
180
+ @failed_auths = 0
181
+ end
182
+
183
+ def failed_auth
184
+ @failed_auths += 1
185
+ sleep @config.failed_login_delay
186
+ if @config.max_failed_logins && @failed_auths >= @config.max_failed_logins
187
+ reply "421 server unavailable"
188
+ throw :done
189
+ end
190
+ end
191
+
192
+ def set_active_mode_address(address, port)
193
+ if port > 0xffff || port < 1024 && !@config.allow_low_data_ports
194
+ error "Command not implemented for that parameter", 504
195
+ end
196
+ @data_hostname = address
197
+ @data_port = port
198
+ end
199
+
200
+ def initialize_session
201
+ @logged_in = false
202
+ @data_type = 'A'
203
+ @mode = 'S'
204
+ @structure = 'F'
205
+ @name_prefix = '/'
206
+ @data_channel_protection_level = :clear
207
+ @data_hostname = nil
208
+ @data_port = nil
209
+ @protection_buffer_size_set = false
210
+ @epsv_all = false
211
+ close_data_server_socket
212
+ reset_failed_auths
213
+ end
214
+
215
+ def server_name_and_version
216
+ "#{@config.server_name} #{@config.server_version}"
217
+ end
218
+
219
+ end
220
+ end
@@ -0,0 +1,111 @@
1
+ module Ftpd
2
+
3
+ # All of the configuration needed by a session
4
+
5
+ class SessionConfig
6
+
7
+ # If true, allow the PORT command to specify privileged data ports
8
+ # (those below 1024). Defaults to false. Setting this to true
9
+ # makes it easier for an attacker to use the server to attack
10
+ # another server. See RFC 2577 section 3.
11
+ #
12
+ # @return [Boolean]
13
+
14
+ attr_accessor :allow_low_data_ports
15
+
16
+ # The authentication level. One of:
17
+ #
18
+ # * Ftpd::AUTH_USER
19
+ # * Ftpd::AUTH_PASSWORD (default)
20
+ # * Ftpd::AUTH_ACCOUNT
21
+ #
22
+ # @return [Integer] The authentication level
23
+
24
+ attr_accessor :auth_level
25
+
26
+ # @return driver A driver for the server's dynamic behavior such
27
+ # as authentication and file system access.
28
+ #
29
+ # The driver should expose these public methods:
30
+ # * {Example::Driver#authenticate authenticate}
31
+ # * {Example::Driver#file_system file_system}
32
+
33
+ attr_accessor :driver
34
+
35
+ # The delay (in seconds) after a failed login. Defaults to 0.
36
+ # Setting this makes brute force password guessing less efficient
37
+ # for the attacker. RFC-2477 suggests a delay of 5 seconds.
38
+
39
+ attr_accessor :failed_login_delay
40
+
41
+ # The class for formatting for LIST output.
42
+ #
43
+ # @return [class that quacks like Ftpd::ListFormat::Ls]
44
+
45
+ attr_accessor :list_formatter
46
+
47
+ # The logger.
48
+ #
49
+ # @return [Logger]
50
+
51
+ attr_accessor :log
52
+
53
+ # The maximum number of failed login attempts before disconnecting
54
+ # the user. Defaults to nil (no maximum). When set, this may
55
+ # makes brute-force password guessing attack less efficient.
56
+ #
57
+ # @return [Integer]
58
+
59
+ attr_accessor :max_failed_logins
60
+
61
+ # The number of seconds to delay before replying. This is for
62
+ # testing, when you need to test, for example, client timeouts.
63
+ # Defaults to 0 (no delay).
64
+ #
65
+ # @return [Numeric]
66
+
67
+ attr_accessor :response_delay
68
+
69
+ # The server's name, sent in a STAT reply. Defaults to
70
+ # {Ftpd::FtpServer::DEFAULT_SERVER_NAME}.
71
+ #
72
+ # @return [String]
73
+
74
+ attr_accessor :server_name
75
+
76
+ # The server's version, sent in a STAT reply.
77
+ #
78
+ # @return [String]
79
+
80
+ attr_accessor :server_version
81
+
82
+ # The session timeout. When a session is awaiting a command, if
83
+ # one is not received in this many seconds, the session is
84
+ # disconnected. If nil, then timeout is disabled.
85
+ #
86
+ # @return [Numeric]
87
+
88
+ attr_accessor :session_timeout
89
+
90
+ # Whether or not to do TLS, and which flavor.
91
+ #
92
+ # One of:
93
+ # * :off
94
+ # * :explicit
95
+ # * :implicit
96
+ #
97
+ # @return [Symbol]
98
+
99
+ attr_accessor :tls
100
+
101
+ # The exception handler. When there is an unknown exception,
102
+ # server replies 451 and calls exception_handler. If nil,
103
+ # then it's ignored.
104
+ #
105
+ # @return [Proc]
106
+
107
+ attr_accessor :exception_handler
108
+
109
+ end
110
+
111
+ end
@@ -0,0 +1,80 @@
1
+ module Ftpd
2
+
3
+ class Stream
4
+
5
+ CHUNK_SIZE = 1024 * 100 # 100kb
6
+
7
+ attr_reader :data_type
8
+ attr_reader :byte_count
9
+
10
+ # @param io [IO] The stream to read from or write to
11
+ # @param data_type [String] The FTP data type of the stream
12
+
13
+ def initialize(io, data_type)
14
+ @io, @data_type = io, data_type
15
+ @byte_count = 0
16
+ end
17
+
18
+ # Read and convert a chunk of up to CHUNK_SIZE from the stream
19
+ # @return [String] if any bytes remain to read from the stream
20
+ # @return [NilClass] if no bytes remain
21
+
22
+ def read
23
+ chunk = converted_chunk(@io)
24
+ return unless chunk
25
+ chunk = nvt_ascii_to_unix(chunk) if data_type == 'A'
26
+ record_bytes(chunk)
27
+ chunk
28
+ end
29
+
30
+ # Convert and write a chunk of up to CHUNK_SIZE to the stream from the
31
+ # provided IO object
32
+ #
33
+ # @param io [IO] The data to be written to the stream
34
+
35
+ def write(io)
36
+ while chunk = converted_chunk(io)
37
+ chunk = unix_to_nvt_ascii(chunk) if data_type == 'A'
38
+ result = @io.write(chunk)
39
+ record_bytes(chunk)
40
+ result
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ # We never want to break up any \r\n sequences in the file. To avoid
47
+ # this in an efficient way, we always pull an "extra" character from the
48
+ # stream and add it to the buffer. If the character is a \r, then we put
49
+ # it back onto the stream instead of adding it to the buffer.
50
+
51
+ def converted_chunk(io)
52
+ chunk = io.read(CHUNK_SIZE)
53
+ return unless chunk
54
+ if data_type == 'A'
55
+ next_char = io.getc
56
+ if next_char == "\r"
57
+ io.ungetc(next_char)
58
+ elsif next_char
59
+ chunk += next_char
60
+ end
61
+ end
62
+ chunk
63
+ end
64
+
65
+ def unix_to_nvt_ascii(s)
66
+ return s if s =~ /\r\n/
67
+ s.gsub(/\n/, "\r\n")
68
+ end
69
+
70
+ def nvt_ascii_to_unix(s)
71
+ s.gsub(/\r\n/, "\n")
72
+ end
73
+
74
+ def record_bytes(chunk)
75
+ @byte_count += chunk.size if chunk
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,114 @@
1
+ # -*- ruby encoding: us-ascii -*-
2
+
3
+ module Ftpd
4
+
5
+ # Handle the limited processing of Telnet sequences required by the
6
+ # FTP RFCs.
7
+ #
8
+ # Telnet option processing is quite complex, but we need do only a
9
+ # simple subset of it, since we can disagree with any request by the
10
+ # client to turn on an option (RFC-1123 4.1.2.12). Adhering to
11
+ # RFC-1143 ("The Q Method of Implementing TELNET Option Negiation"),
12
+ # and supporting only what's needed to keep all options turned off:
13
+ #
14
+ # * Reply to WILL sequence with DONT sequence
15
+ # * Reply to DO sequence with WONT sequence
16
+ # * Ignore WONT sequence
17
+ # * Ignore DONT sequence
18
+ #
19
+ # We also handle the "interrupt process" and "data mark" sequences,
20
+ # which the client sends before the ABORT command, by ignoring them.
21
+ #
22
+ # All Telnet sequence start with an IAC, followed by at least one
23
+ # character. Here are the sequences we care about:
24
+ #
25
+ # SEQUENCE CODES
26
+ # ----------------- --------------------
27
+ # WILL IAC WILL option-code
28
+ # WONT IAC WONT option-code
29
+ # DO IAC DO option-code
30
+ # DONT IAC DONT option-code
31
+ # escaped 255 IAC IAC
32
+ # interrupt process IAC IP
33
+ # data mark IAC DM
34
+ #
35
+ # Any pathalogical sequence (e.g. IAC + \x01), or any sequence we
36
+ # don't recognize, we pass through.
37
+
38
+ class Telnet
39
+
40
+ # The command with recognized Telnet sequences removed
41
+
42
+ attr_reader :plain
43
+
44
+ # Any Telnet sequences to send
45
+
46
+ attr_reader :reply
47
+
48
+ # Create a new instance with a command that may contain Telnet
49
+ # sequences.
50
+ # @param command [String]
51
+
52
+ def initialize(command)
53
+ parse_command command
54
+ end
55
+
56
+ private
57
+
58
+ module Codes
59
+ IAC = 255.chr # 0xff
60
+ DONT = 254.chr # 0xfe
61
+ DO = 253.chr # 0xfd
62
+ WONT = 252.chr # 0xfc
63
+ WILL = 251.chr # 0xfb
64
+ IP = 244.chr # 0xf4
65
+ DM = 242.chr # 0xf2
66
+ end
67
+ include Codes
68
+
69
+ def accept(scanner)
70
+ @plain << scanner[1]
71
+ end
72
+
73
+ def reply_dont(scanner)
74
+ @reply << IAC + DONT + scanner[1]
75
+ end
76
+
77
+ def reply_wont(scanner)
78
+ @reply << IAC + WONT + scanner[1]
79
+ end
80
+
81
+ def ignore(scanner)
82
+ end
83
+
84
+ # Telnet sequences to handle, and how to handle them
85
+
86
+ SEQUENCES = [
87
+ [/#{IAC}(#{IAC})/, :accept],
88
+ [/#{IAC}#{WILL}(.)/m, :reply_dont],
89
+ [/#{IAC}#{WONT}(.)/m, :ignore],
90
+ [/#{IAC}#{DO}(.)/m, :reply_wont],
91
+ [/#{IAC}#{DONT}(.)/m, :ignore],
92
+ [/#{IAC}#{IP}/, :ignore],
93
+ [/#{IAC}#{DM}/, :ignore],
94
+ [/(.)/m, :accept],
95
+ ]
96
+
97
+ # Parse the the command. Sets @plain and @reply
98
+
99
+ def parse_command(command)
100
+ @plain = ''
101
+ @reply = ''
102
+ scanner = StringScanner.new(command)
103
+ while !scanner.eos?
104
+ SEQUENCES.each do |regexp, method|
105
+ if scanner.scan(regexp)
106
+ send method, scanner
107
+ break
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ end
114
+ end