ftpd 0.11.0 → 0.12.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 (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