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,27 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdNlst < CommandHandler
6
+
7
+ def cmd_nlst(argument)
8
+ close_data_server_socket_when_done do
9
+ ensure_logged_in
10
+ ensure_file_system_supports :dir
11
+ path = list_path(argument)
12
+ path = File.expand_path(path, name_prefix)
13
+ transmit_file(name_list(path), 'A')
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def name_list(path)
20
+ path_list(path).map do |path|
21
+ File.basename(path) + "\n"
22
+ end.join
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdNoop < CommandHandler
6
+
7
+ def cmd_noop(argument)
8
+ syntax_error if argument
9
+ reply "200 Nothing done"
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,14 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdOpts < CommandHandler
6
+
7
+ def cmd_opts(argument)
8
+ syntax_error unless argument
9
+ error '501 Unsupported option'
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdPasv < CommandHandler
6
+
7
+ def cmd_pasv(argument)
8
+ ensure_logged_in
9
+ ensure_not_epsv_all
10
+ if data_server
11
+ reply "200 Already in passive mode"
12
+ else
13
+ interface = socket.addr[3]
14
+ self.data_server = TCPServer.new(interface, 0)
15
+ ip = data_server.addr[3]
16
+ port = data_server.addr[1]
17
+ quads = [
18
+ ip.scan(/\d+/),
19
+ port >> 8,
20
+ port & 0xff,
21
+ ].flatten.join(',')
22
+ reply "227 Entering passive mode (#{quads})"
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdPbsz < CommandHandler
6
+
7
+ def cmd_pbsz(buffer_size)
8
+ ensure_tls_supported
9
+ syntax_error unless buffer_size =~ /^\d+$/
10
+ buffer_size = buffer_size.to_i
11
+ unless socket.encrypted?
12
+ error "503 PBSZ must be preceded by AUTH"
13
+ end
14
+ unless buffer_size == 0
15
+ error "501 PBSZ=0"
16
+ end
17
+ reply "200 PBSZ=0"
18
+ self.protection_buffer_size_set = true
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,28 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ # The Data Port (PORT) command.
6
+
7
+ class CmdPort < CommandHandler
8
+
9
+ def cmd_port(argument)
10
+ ensure_logged_in
11
+ ensure_not_epsv_all
12
+ pieces = argument.split(/,/)
13
+ syntax_error unless pieces.size == 6
14
+ pieces.collect! do |s|
15
+ syntax_error unless s =~ /^\d{1,3}$/
16
+ i = s.to_i
17
+ syntax_error unless (0..255) === i
18
+ i
19
+ end
20
+ hostname = pieces[0..3].join('.')
21
+ port = pieces[4] << 8 | pieces[5]
22
+ set_active_mode_address hostname, port
23
+ reply "200 PORT command successful"
24
+ end
25
+
26
+ end
27
+
28
+ end
@@ -0,0 +1,34 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdProt < CommandHandler
6
+
7
+ def cmd_prot(level_arg)
8
+ level_code = level_arg.upcase
9
+ unless protection_buffer_size_set
10
+ error "503 PROT must be preceded by PBSZ"
11
+ end
12
+ level = DATA_CHANNEL_PROTECTION_LEVELS[level_code]
13
+ unless level
14
+ error "504 Unknown protection level"
15
+ end
16
+ unless level == :private
17
+ error "536 Unsupported protection level #{level}"
18
+ end
19
+ self.data_channel_protection_level = level
20
+ reply "200 Data protection level #{level_code}"
21
+ end
22
+
23
+ private
24
+
25
+ DATA_CHANNEL_PROTECTION_LEVELS = {
26
+ 'C'=>:clear,
27
+ 'S'=>:safe,
28
+ 'E'=>:confidential,
29
+ 'P'=>:private
30
+ }
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdPwd < CommandHandler
6
+
7
+ def cmd_pwd(argument)
8
+ ensure_logged_in
9
+ pwd 257
10
+ end
11
+ alias cmd_xpwd :cmd_pwd
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,18 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ # The Quit (QUIT) command
6
+
7
+ class CmdQuit < CommandHandler
8
+
9
+ def cmd_quit(argument)
10
+ syntax_error if argument
11
+ ensure_logged_in
12
+ reply "221 Byebye"
13
+ self.logged_in = false
14
+ end
15
+
16
+ end
17
+
18
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdRein < CommandHandler
6
+
7
+ def cmd_rein(argument)
8
+ unimplemented_error
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdRename < CommandHandler
6
+
7
+ def cmd_rnfr(argument)
8
+ ensure_logged_in
9
+ ensure_file_system_supports :rename
10
+ syntax_error unless argument
11
+ from_path = File.expand_path(argument, name_prefix)
12
+ ensure_accessible from_path
13
+ ensure_exists from_path
14
+ @rename_from_path = from_path
15
+ reply '350 RNFR accepted; ready for destination'
16
+ expect 'rnto'
17
+ end
18
+
19
+ def cmd_rnto(argument)
20
+ ensure_logged_in
21
+ ensure_file_system_supports :rename
22
+ syntax_error unless argument
23
+ to_path = File.expand_path(argument, name_prefix)
24
+ ensure_accessible to_path
25
+ ensure_does_not_exist to_path
26
+ file_system.rename(@rename_from_path, to_path)
27
+ reply '250 Rename successful'
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdRest < CommandHandler
6
+
7
+ def cmd_rest(argument)
8
+ unimplemented_error
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdRetr < CommandHandler
6
+
7
+ def cmd_retr(argument)
8
+ close_data_server_socket_when_done do
9
+ ensure_logged_in
10
+ ensure_file_system_supports :read
11
+ path = argument
12
+ syntax_error unless path
13
+ path = File.expand_path(path, name_prefix)
14
+ ensure_accessible path
15
+ ensure_exists path
16
+ contents = file_system.read(path)
17
+ transmit_file contents
18
+ end
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -0,0 +1,22 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdRmd < CommandHandler
6
+
7
+ def cmd_rmd(argument)
8
+ syntax_error unless argument
9
+ ensure_logged_in
10
+ ensure_file_system_supports :rmdir
11
+ path = File.expand_path(argument, name_prefix)
12
+ ensure_accessible path
13
+ ensure_exists path
14
+ ensure_directory path
15
+ file_system.rmdir path
16
+ reply '250 RMD command successful'
17
+ end
18
+ alias cmd_xrmd :cmd_rmd
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdSite < CommandHandler
6
+
7
+ def cmd_site(argument)
8
+ unimplemented_error
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdSize < CommandHandler
6
+
7
+ def cmd_size(path)
8
+ ensure_logged_in
9
+ ensure_file_system_supports :read
10
+ syntax_error unless path
11
+ path = File.expand_path(path, name_prefix)
12
+ ensure_accessible(path)
13
+ ensure_exists(path)
14
+ contents = file_system.read(path)
15
+ contents = (data_type == 'A') ? unix_to_nvt_ascii(contents) : contents
16
+ reply "213 #{contents.bytesize}"
17
+ end
18
+
19
+ end
20
+
21
+ end
@@ -0,0 +1,13 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdSmnt < CommandHandler
6
+
7
+ def cmd_smnt(argument)
8
+ unimplemented_error
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,15 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdStat < CommandHandler
6
+
7
+ def cmd_stat(argument)
8
+ ensure_logged_in
9
+ syntax_error if argument
10
+ reply "211 #{server_name_and_version}"
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdStor < CommandHandler
6
+
7
+ def cmd_stor(argument)
8
+ close_data_server_socket_when_done do
9
+ ensure_logged_in
10
+ ensure_file_system_supports :write
11
+ path = argument
12
+ syntax_error unless path
13
+ path = File.expand_path(path, name_prefix)
14
+ ensure_accessible path
15
+ ensure_exists File.dirname(path)
16
+ contents = receive_file
17
+ file_system.write path, contents
18
+ reply "226 Transfer complete"
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,24 @@
1
+ require_relative 'command_handler'
2
+
3
+ module Ftpd
4
+
5
+ class CmdStou < CommandHandler
6
+
7
+ def cmd_stou(argument)
8
+ close_data_server_socket_when_done do
9
+ ensure_logged_in
10
+ ensure_file_system_supports :write
11
+ path = argument || 'ftpd'
12
+ path = File.expand_path(path, name_prefix)
13
+ path = unique_path(path)
14
+ ensure_accessible path
15
+ ensure_exists File.dirname(path)
16
+ contents = receive_file(File.basename(path))
17
+ file_system.write path, contents
18
+ reply "226 Transfer complete"
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end