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.
- checksums.yaml +4 -4
- data/Changelog.md +7 -1
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +28 -4
- data/VERSION +1 -1
- data/examples/foo.rb +114 -0
- data/ftpd.gemspec +56 -8
- data/lib/ftpd.rb +77 -31
- data/lib/ftpd/ascii_helper.rb +16 -0
- data/lib/ftpd/cmd_abor.rb +13 -0
- data/lib/ftpd/cmd_allo.rb +20 -0
- data/lib/ftpd/cmd_appe.rb +23 -0
- data/lib/ftpd/cmd_auth.rb +21 -0
- data/lib/ftpd/cmd_cdup.rb +16 -0
- data/lib/ftpd/cmd_cwd.rb +20 -0
- data/lib/ftpd/cmd_dele.rb +21 -0
- data/lib/ftpd/cmd_eprt.rb +23 -0
- data/lib/ftpd/cmd_epsv.rb +30 -0
- data/lib/ftpd/cmd_feat.rb +44 -0
- data/lib/ftpd/cmd_help.rb +29 -0
- data/lib/ftpd/cmd_list.rb +33 -0
- data/lib/ftpd/cmd_login.rb +60 -0
- data/lib/ftpd/cmd_mdtm.rb +27 -0
- data/lib/ftpd/cmd_mkd.rb +23 -0
- data/lib/ftpd/cmd_mode.rb +27 -0
- data/lib/ftpd/cmd_nlst.rb +27 -0
- data/lib/ftpd/cmd_noop.rb +14 -0
- data/lib/ftpd/cmd_opts.rb +14 -0
- data/lib/ftpd/cmd_pasv.rb +28 -0
- data/lib/ftpd/cmd_pbsz.rb +23 -0
- data/lib/ftpd/cmd_port.rb +28 -0
- data/lib/ftpd/cmd_prot.rb +34 -0
- data/lib/ftpd/cmd_pwd.rb +15 -0
- data/lib/ftpd/cmd_quit.rb +18 -0
- data/lib/ftpd/cmd_rein.rb +13 -0
- data/lib/ftpd/cmd_rename.rb +32 -0
- data/lib/ftpd/cmd_rest.rb +13 -0
- data/lib/ftpd/cmd_retr.rb +23 -0
- data/lib/ftpd/cmd_rmd.rb +22 -0
- data/lib/ftpd/cmd_site.rb +13 -0
- data/lib/ftpd/cmd_size.rb +21 -0
- data/lib/ftpd/cmd_smnt.rb +13 -0
- data/lib/ftpd/cmd_stat.rb +15 -0
- data/lib/ftpd/cmd_stor.rb +24 -0
- data/lib/ftpd/cmd_stou.rb +24 -0
- data/lib/ftpd/cmd_stru.rb +27 -0
- data/lib/ftpd/cmd_syst.rb +16 -0
- data/lib/ftpd/cmd_type.rb +28 -0
- data/lib/ftpd/command_handler.rb +91 -0
- data/lib/ftpd/command_handler_factory.rb +51 -0
- data/lib/ftpd/command_handlers.rb +60 -0
- data/lib/ftpd/command_loop.rb +77 -0
- data/lib/ftpd/data_connection_helper.rb +124 -0
- data/lib/ftpd/disk_file_system.rb +22 -6
- data/lib/ftpd/error.rb +4 -0
- data/lib/ftpd/file_system_helper.rb +67 -0
- data/lib/ftpd/ftp_server.rb +1 -1
- data/lib/ftpd/server.rb +1 -0
- data/lib/ftpd/session.rb +31 -749
- data/lib/ftpd/tls_server.rb +2 -0
- data/spec/server_spec.rb +66 -0
- metadata +81 -30
@@ -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
|
data/lib/ftpd/cmd_cwd.rb
ADDED
@@ -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
|
data/lib/ftpd/cmd_mkd.rb
ADDED
@@ -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
|