ftpd 0.11.0 → 0.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +7 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +1 -1
  5. data/README.md +28 -4
  6. data/VERSION +1 -1
  7. data/examples/foo.rb +114 -0
  8. data/ftpd.gemspec +56 -8
  9. data/lib/ftpd.rb +77 -31
  10. data/lib/ftpd/ascii_helper.rb +16 -0
  11. data/lib/ftpd/cmd_abor.rb +13 -0
  12. data/lib/ftpd/cmd_allo.rb +20 -0
  13. data/lib/ftpd/cmd_appe.rb +23 -0
  14. data/lib/ftpd/cmd_auth.rb +21 -0
  15. data/lib/ftpd/cmd_cdup.rb +16 -0
  16. data/lib/ftpd/cmd_cwd.rb +20 -0
  17. data/lib/ftpd/cmd_dele.rb +21 -0
  18. data/lib/ftpd/cmd_eprt.rb +23 -0
  19. data/lib/ftpd/cmd_epsv.rb +30 -0
  20. data/lib/ftpd/cmd_feat.rb +44 -0
  21. data/lib/ftpd/cmd_help.rb +29 -0
  22. data/lib/ftpd/cmd_list.rb +33 -0
  23. data/lib/ftpd/cmd_login.rb +60 -0
  24. data/lib/ftpd/cmd_mdtm.rb +27 -0
  25. data/lib/ftpd/cmd_mkd.rb +23 -0
  26. data/lib/ftpd/cmd_mode.rb +27 -0
  27. data/lib/ftpd/cmd_nlst.rb +27 -0
  28. data/lib/ftpd/cmd_noop.rb +14 -0
  29. data/lib/ftpd/cmd_opts.rb +14 -0
  30. data/lib/ftpd/cmd_pasv.rb +28 -0
  31. data/lib/ftpd/cmd_pbsz.rb +23 -0
  32. data/lib/ftpd/cmd_port.rb +28 -0
  33. data/lib/ftpd/cmd_prot.rb +34 -0
  34. data/lib/ftpd/cmd_pwd.rb +15 -0
  35. data/lib/ftpd/cmd_quit.rb +18 -0
  36. data/lib/ftpd/cmd_rein.rb +13 -0
  37. data/lib/ftpd/cmd_rename.rb +32 -0
  38. data/lib/ftpd/cmd_rest.rb +13 -0
  39. data/lib/ftpd/cmd_retr.rb +23 -0
  40. data/lib/ftpd/cmd_rmd.rb +22 -0
  41. data/lib/ftpd/cmd_site.rb +13 -0
  42. data/lib/ftpd/cmd_size.rb +21 -0
  43. data/lib/ftpd/cmd_smnt.rb +13 -0
  44. data/lib/ftpd/cmd_stat.rb +15 -0
  45. data/lib/ftpd/cmd_stor.rb +24 -0
  46. data/lib/ftpd/cmd_stou.rb +24 -0
  47. data/lib/ftpd/cmd_stru.rb +27 -0
  48. data/lib/ftpd/cmd_syst.rb +16 -0
  49. data/lib/ftpd/cmd_type.rb +28 -0
  50. data/lib/ftpd/command_handler.rb +91 -0
  51. data/lib/ftpd/command_handler_factory.rb +51 -0
  52. data/lib/ftpd/command_handlers.rb +60 -0
  53. data/lib/ftpd/command_loop.rb +77 -0
  54. data/lib/ftpd/data_connection_helper.rb +124 -0
  55. data/lib/ftpd/disk_file_system.rb +22 -6
  56. data/lib/ftpd/error.rb +4 -0
  57. data/lib/ftpd/file_system_helper.rb +67 -0
  58. data/lib/ftpd/ftp_server.rb +1 -1
  59. data/lib/ftpd/server.rb +1 -0
  60. data/lib/ftpd/session.rb +31 -749
  61. data/lib/ftpd/tls_server.rb +2 -0
  62. data/spec/server_spec.rb +66 -0
  63. metadata +81 -30
@@ -0,0 +1,16 @@
1
+ module Ftpd
2
+
3
+ module AsciiHelper
4
+
5
+ def unix_to_nvt_ascii(s)
6
+ return s if s =~ /\r\n/
7
+ s.gsub(/\n/, "\r\n")
8
+ end
9
+
10
+ def nvt_ascii_to_unix(s)
11
+ s.gsub(/\r\n/, "\n")
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdAbor < CommandHandler
6
+
7
+ def cmd_abor(argument)
8
+ unimplemented_error
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ # The Allocate (ALLO) command.
6
+ #
7
+ # This server does not need the ALLO command, so treats it as a
8
+ # NOOP.
9
+
10
+ class CmdAllo < CommandHandler
11
+
12
+ def cmd_allo(argument)
13
+ ensure_logged_in
14
+ syntax_error unless argument =~ /^\d+( R \d+)?$/
15
+ command_not_needed
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdAppe < CommandHandler
6
+
7
+ def cmd_appe(argument)
8
+ close_data_server_socket_when_done do
9
+ ensure_logged_in
10
+ ensure_file_system_supports :append
11
+ path = argument
12
+ syntax_error unless path
13
+ path = File.expand_path(path, name_prefix)
14
+ ensure_accessible path
15
+ contents = receive_file
16
+ file_system.append path, contents
17
+ reply "226 Transfer complete"
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdAuth < CommandHandler
6
+
7
+ def cmd_auth(security_scheme)
8
+ ensure_tls_supported
9
+ if socket.encrypted?
10
+ error "503 AUTH already done"
11
+ end
12
+ unless security_scheme =~ /^TLS(-C)?$/i
13
+ error "504 Security scheme not implemented: #{security_scheme}"
14
+ end
15
+ reply "234 AUTH #{security_scheme} OK."
16
+ socket.encrypt
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdCdup < CommandHandler
6
+
7
+ def cmd_cdup(argument)
8
+ syntax_error if argument
9
+ ensure_logged_in
10
+ execute_command 'cwd', '..'
11
+ end
12
+ alias cmd_xcup :cmd_cdup
13
+
14
+ end
15
+
16
+ end
@@ -0,0 +1,20 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdCwd < CommandHandler
6
+
7
+ def cmd_cwd(argument)
8
+ ensure_logged_in
9
+ path = File.expand_path(argument, name_prefix)
10
+ ensure_accessible path
11
+ ensure_exists path
12
+ ensure_directory path
13
+ self.name_prefix = path
14
+ pwd 250
15
+ end
16
+ alias cmd_xcwd :cmd_cwd
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdDele < CommandHandler
6
+
7
+ def cmd_dele(argument)
8
+ ensure_logged_in
9
+ ensure_file_system_supports :delete
10
+ path = argument
11
+ error "501 Path required" unless path
12
+ path = File.expand_path(path, name_prefix)
13
+ ensure_accessible path
14
+ ensure_exists path
15
+ file_system.delete path
16
+ reply "250 DELE command successful"
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdEprt < CommandHandler
6
+
7
+ def cmd_eprt(argument)
8
+ ensure_logged_in
9
+ ensure_not_epsv_all
10
+ delim = argument[0..0]
11
+ parts = argument.split(delim)[1..-1]
12
+ syntax_error unless parts.size == 3
13
+ protocol_code, address, port = *parts
14
+ protocol_code = protocol_code.to_i
15
+ ensure_protocol_supported protocol_code
16
+ port = port.to_i
17
+ set_active_mode_address address, port
18
+ reply "200 EPRT command successful"
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,30 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdEpsv < CommandHandler
6
+
7
+ def cmd_epsv(argument)
8
+ ensure_logged_in
9
+ if data_server
10
+ reply "200 Already in passive mode"
11
+ else
12
+ if argument == 'ALL'
13
+ self.epsv_all = true
14
+ reply "220 EPSV now required for port setup"
15
+ else
16
+ protocol_code = argument && argument.to_i
17
+ if protocol_code
18
+ ensure_protocol_supported protocol_code
19
+ end
20
+ interface = socket.addr[3]
21
+ self.data_server = TCPServer.new(interface, 0)
22
+ port = data_server.addr[1]
23
+ reply "229 Entering extended passive mode (|||#{port}|)"
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,44 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdFeat < CommandHandler
6
+
7
+ def cmd_feat(argument)
8
+ syntax_error if argument
9
+ reply '211-Extensions supported:'
10
+ extensions.each do |extension|
11
+ reply " #{extension}"
12
+ end
13
+ reply '211 END'
14
+ end
15
+
16
+ private
17
+
18
+ def extensions
19
+ [
20
+ (TLS_EXTENSIONS if tls_enabled?),
21
+ IPV6_EXTENSIONS,
22
+ RFC_3659_EXTENSIONS,
23
+ ].flatten.compact
24
+ end
25
+
26
+ TLS_EXTENSIONS = [
27
+ 'AUTH TLS',
28
+ 'PBSZ',
29
+ 'PROT'
30
+ ]
31
+
32
+ IPV6_EXTENSIONS = [
33
+ 'EPRT',
34
+ 'EPSV',
35
+ ]
36
+
37
+ RFC_3659_EXTENSIONS = [
38
+ 'MDTM',
39
+ 'SIZE',
40
+ ]
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdHelp < CommandHandler
6
+
7
+ def cmd_help(argument)
8
+ if argument
9
+ command = argument.upcase
10
+ if supported_commands.include?(command)
11
+ reply "214 Command #{command} is recognized"
12
+ else
13
+ reply "214 Command #{command} is not recognized"
14
+ end
15
+ else
16
+ reply '214-The following commands are recognized:'
17
+ supported_commands.sort.each_slice(8) do |commands|
18
+ line = commands.map do |command|
19
+ ' %-4s' % command
20
+ end.join
21
+ reply line
22
+ end
23
+ reply '214 Have a nice day.'
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,33 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdList < CommandHandler
6
+
7
+ def cmd_list(argument)
8
+ close_data_server_socket_when_done do
9
+ ensure_logged_in
10
+ ensure_file_system_supports :dir
11
+ ensure_file_system_supports :file_info
12
+ path = list_path(argument)
13
+ path = File.expand_path(path, name_prefix)
14
+ transmit_file(list(path), 'A')
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def list(path)
21
+ format_list(path_list(path))
22
+ end
23
+
24
+ def format_list(paths)
25
+ paths.map do |path|
26
+ file_info = file_system.file_info(path)
27
+ config.list_formatter.new(file_info).to_s + "\n"
28
+ end.join
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,60 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ # The login commands
6
+
7
+ class CmdLogin < CommandHandler
8
+
9
+ # @return [String] The user for the current login sequence
10
+
11
+ attr_accessor :user
12
+
13
+ # @return [String] The password for the current login sequence
14
+
15
+ attr_accessor :password
16
+
17
+ def initialize(*)
18
+ super
19
+ @user = nil
20
+ @password = nil
21
+ end
22
+
23
+ # * User Name (USER) command.
24
+
25
+ def cmd_user(argument)
26
+ syntax_error unless argument
27
+ sequence_error if logged_in
28
+ @user = argument
29
+ if config.auth_level > AUTH_USER
30
+ reply "331 Password required"
31
+ expect 'pass'
32
+ else
33
+ login @user
34
+ end
35
+ end
36
+
37
+ # The Password (PASS) command
38
+
39
+ def cmd_pass(argument)
40
+ syntax_error unless argument
41
+ @password = argument
42
+ if config.auth_level > AUTH_PASSWORD
43
+ reply "332 Account required"
44
+ expect 'acct'
45
+ else
46
+ login @user, @password
47
+ end
48
+ end
49
+
50
+ # The Account (ACCT) command
51
+
52
+ def cmd_acct(argument)
53
+ syntax_error unless argument
54
+ account = argument
55
+ login @user, @password, account
56
+ end
57
+
58
+ end
59
+
60
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdMdtm < CommandHandler
6
+
7
+ def cmd_mdtm(path)
8
+ ensure_logged_in
9
+ ensure_file_system_supports :dir
10
+ ensure_file_system_supports :file_info
11
+ syntax_error unless path
12
+ path = File.expand_path(path, name_prefix)
13
+ ensure_accessible(path)
14
+ ensure_exists(path)
15
+ info = file_system.file_info(path)
16
+ mtime = info.mtime.utc
17
+ # We would like to report fractional seconds, too. Sadly, the
18
+ # spec declares that we may not report more precision than is
19
+ # actually there, and there is no spec or API to tell us how
20
+ # many fractional digits are significant.
21
+ mtime = mtime.strftime("%Y%m%d%H%M%S")
22
+ reply "213 #{mtime}"
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdMkd < CommandHandler
6
+
7
+ def cmd_mkd(argument)
8
+ syntax_error unless argument
9
+ ensure_logged_in
10
+ ensure_file_system_supports :mkdir
11
+ path = File.expand_path(argument, name_prefix)
12
+ ensure_accessible path
13
+ ensure_exists File.dirname(path)
14
+ ensure_directory File.dirname(path)
15
+ ensure_does_not_exist path
16
+ file_system.mkdir path
17
+ reply %Q'257 "#{path}" created'
18
+ end
19
+ alias cmd_xmkd :cmd_mkd
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,27 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdMode < CommandHandler
6
+
7
+ def cmd_mode(argument)
8
+ syntax_error unless argument
9
+ ensure_logged_in
10
+ name, implemented = TRANSMISSION_MODES[argument]
11
+ error "504 Invalid mode code" unless name
12
+ error "504 Mode not implemented" unless implemented
13
+ self.mode = argument
14
+ reply "200 Mode set to #{name}"
15
+ end
16
+
17
+ private
18
+
19
+ TRANSMISSION_MODES = {
20
+ 'B'=>['Block', false],
21
+ 'C'=>['Compressed', false],
22
+ 'S'=>['Stream', true],
23
+ }
24
+
25
+ end
26
+
27
+ end