ftpd 0.9.0 → 0.10.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/.travis.yml +4 -0
- data/Changelog.md +10 -0
- data/Gemfile.lock +4 -4
- data/README.md +8 -9
- data/VERSION +1 -1
- data/examples/example.rb +18 -10
- data/features/ftp_server/allo.feature +1 -1
- data/features/ftp_server/eprt.feature +1 -0
- data/ftpd.gemspec +7 -4
- data/lib/ftpd.rb +2 -0
- data/lib/ftpd/ftp_server.rb +31 -25
- data/lib/ftpd/server.rb +3 -2
- data/lib/ftpd/session.rb +27 -36
- data/lib/ftpd/session_config.rb +103 -0
- data/lib/ftpd/telnet.rb +38 -43
- data/rake_tasks/jeweler.rake +4 -1
- metadata +6 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c547a0bf777999094a612c387fb63d40a2cbc7f
|
4
|
+
data.tar.gz: 021d0b2bdad238406fdc19d00c9b8036ec85a749
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b7560963a406e9098cbbff5ba71741c642c93ce7f6296218eb28e69027c99837b07891abf922111e84601792871c3296211f66b8865711520b7a0bdc8ac3663
|
7
|
+
data.tar.gz: 5797b248427a0d497de07918094c35db4a921ae016e016ae63f48fef901c8ba4a38064b0314e62037c00dd695f879bf6b0e880441f9701d7ad884328838f5331
|
data/.travis.yml
ADDED
data/Changelog.md
CHANGED
@@ -2,6 +2,16 @@ This is the change log for the main branch of ftpd, which supports
|
|
2
2
|
Ruby 1.9 and greater. For ruby 1.8.7, please use the latest version
|
3
3
|
before 0.8.0.
|
4
4
|
|
5
|
+
### 0.10.0
|
6
|
+
|
7
|
+
Bug fixes
|
8
|
+
|
9
|
+
* Do not die when implicit SSL connection disconnects (issue #13)
|
10
|
+
|
11
|
+
API Changes:
|
12
|
+
|
13
|
+
* Change default interface from "localhost" to "127.0.0.1".
|
14
|
+
|
5
15
|
### 0.9.0
|
6
16
|
|
7
17
|
Enhancements
|
data/Gemfile.lock
CHANGED
@@ -24,9 +24,9 @@ GEM
|
|
24
24
|
nokogiri (~> 1.5.2)
|
25
25
|
oauth2
|
26
26
|
hashie (2.0.5)
|
27
|
-
highline (1.6.
|
27
|
+
highline (1.6.20)
|
28
28
|
httpauth (0.2.0)
|
29
|
-
jeweler (1.8.
|
29
|
+
jeweler (1.8.8)
|
30
30
|
builder
|
31
31
|
bundler (~> 1.0)
|
32
32
|
git (>= 1.2.5)
|
@@ -35,11 +35,11 @@ GEM
|
|
35
35
|
nokogiri (= 1.5.10)
|
36
36
|
rake
|
37
37
|
rdoc
|
38
|
-
json (1.8.
|
38
|
+
json (1.8.1)
|
39
39
|
jwt (0.1.8)
|
40
40
|
multi_json (>= 1.5)
|
41
41
|
memoizer (1.0.1)
|
42
|
-
multi_json (1.8.
|
42
|
+
multi_json (1.8.2)
|
43
43
|
multi_test (0.0.2)
|
44
44
|
multi_xml (0.5.5)
|
45
45
|
multipart-post (1.2.0)
|
data/README.md
CHANGED
@@ -1,15 +1,15 @@
|
|
1
|
-
# Ftpd
|
1
|
+
# Ftpd [](https://codeclimate.com/github/wconrad/ftpd) [](https://travis-ci.org/wconrad/ftpd)
|
2
2
|
|
3
3
|
ftpd is a pure Ruby FTP server library. It supports implicit and
|
4
4
|
explicit TLS, passive and active mode, and is unconditionally
|
5
|
-
compliant per [RFC-1123][1]. It
|
6
|
-
embedded in a program.
|
5
|
+
compliant per [RFC-1123][1]. It can be used as part of a test fixture
|
6
|
+
or embedded in a program.
|
7
7
|
|
8
8
|
## A note about this README
|
9
9
|
|
10
|
-
This readme
|
11
|
-
|
12
|
-
|
10
|
+
This readme contains [Yardoc](http://yardoc.org/) markup for links to
|
11
|
+
the API docs; those links don't display properly on github. You'll
|
12
|
+
find a properly rendered version [on
|
13
13
|
rubydoc.info](http://rubydoc.info/gems/ftpd)
|
14
14
|
|
15
15
|
## The state of this library
|
@@ -57,7 +57,6 @@ end
|
|
57
57
|
Dir.mktmpdir do |temp_dir|
|
58
58
|
driver = Driver.new(temp_dir)
|
59
59
|
server = Ftpd::FtpServer.new(driver)
|
60
|
-
server.interface = '127.0.0.1'
|
61
60
|
server.start
|
62
61
|
puts "Server listening on port #{server.bound_port}"
|
63
62
|
gets
|
@@ -172,7 +171,6 @@ example, to set the session timeout to 10 minutes:
|
|
172
171
|
```ruby
|
173
172
|
server = Ftpd::FtpServer.new(driver)
|
174
173
|
server.session_timeout = 10 * 60
|
175
|
-
server.interface = '127.0.0.1'
|
176
174
|
server.start
|
177
175
|
```
|
178
176
|
|
@@ -223,7 +221,8 @@ And register your class with the ftp_server before starting it:
|
|
223
221
|
### Logging
|
224
222
|
|
225
223
|
Ftpd can write to an instance of
|
226
|
-
{http://www.ruby-doc.org/stdlib-2.0.0/libdoc/logger/rdoc/Logger.html
|
224
|
+
{http://www.ruby-doc.org/stdlib-2.0.0/libdoc/logger/rdoc/Logger.html
|
225
|
+
Logger} that you provide. To log to standard out:
|
227
226
|
|
228
227
|
server.log = Logger.new($stdout)
|
229
228
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.10.0
|
data/examples/example.rb
CHANGED
@@ -165,6 +165,19 @@ module Example
|
|
165
165
|
@driver = Driver.new(user, password, account,
|
166
166
|
@data_dir, @args.read_only)
|
167
167
|
@server = Ftpd::FtpServer.new(@driver)
|
168
|
+
configure_server
|
169
|
+
@server.start
|
170
|
+
display_connection_info
|
171
|
+
create_connection_script
|
172
|
+
end
|
173
|
+
|
174
|
+
def run
|
175
|
+
wait_until_stopped
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def configure_server
|
168
181
|
@server.interface = @args.interface
|
169
182
|
@server.port = @args.port
|
170
183
|
@server.tls = @args.tls
|
@@ -175,17 +188,8 @@ module Example
|
|
175
188
|
@server.auth_level = auth_level
|
176
189
|
@server.session_timeout = @args.session_timeout
|
177
190
|
@server.log = make_log
|
178
|
-
@server.start
|
179
|
-
display_connection_info
|
180
|
-
create_connection_script
|
181
191
|
end
|
182
192
|
|
183
|
-
def run
|
184
|
-
wait_until_stopped
|
185
|
-
end
|
186
|
-
|
187
|
-
private
|
188
|
-
|
189
193
|
def auth_level
|
190
194
|
Ftpd.const_get("AUTH_#{@args.auth_level.upcase}")
|
191
195
|
end
|
@@ -212,10 +216,14 @@ module Example
|
|
212
216
|
puts "Account: #{account.inspect}" if auth_level >= Ftpd::AUTH_ACCOUNT
|
213
217
|
puts "TLS: #{@args.tls}"
|
214
218
|
puts "Directory: #{@data_dir}"
|
215
|
-
puts "URI:
|
219
|
+
puts "URI: #{uri}"
|
216
220
|
puts "PID: #{$$}"
|
217
221
|
end
|
218
222
|
|
223
|
+
def uri
|
224
|
+
"ftp://#{connection_host}:#{@server.bound_port}"
|
225
|
+
end
|
226
|
+
|
219
227
|
def create_connection_script
|
220
228
|
command_path = '/tmp/connect-to-example-ftp-server.sh'
|
221
229
|
File.open(command_path, 'w') do |file|
|
data/ftpd.gemspec
CHANGED
@@ -2,21 +2,23 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: ftpd 0.10.0 ruby lib
|
5
6
|
|
6
7
|
Gem::Specification.new do |s|
|
7
8
|
s.name = "ftpd"
|
8
|
-
s.version = "0.
|
9
|
+
s.version = "0.10.0"
|
9
10
|
|
10
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
12
|
s.authors = ["Wayne Conrad"]
|
12
|
-
s.date = "2013-10-
|
13
|
-
s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and is unconditionally compliant per [RFC-1123][1]. It
|
13
|
+
s.date = "2013-10-26"
|
14
|
+
s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and is unconditionally compliant per [RFC-1123][1]. It can be used as part of a test fixture or embedded in a program."
|
14
15
|
s.email = "wconrad@yagni.com"
|
15
16
|
s.extra_rdoc_files = [
|
16
17
|
"LICENSE.md",
|
17
18
|
"README.md"
|
18
19
|
]
|
19
20
|
s.files = [
|
21
|
+
".travis.yml",
|
20
22
|
".yardopts",
|
21
23
|
"Changelog.md",
|
22
24
|
"Gemfile",
|
@@ -155,6 +157,7 @@ Gem::Specification.new do |s|
|
|
155
157
|
"lib/ftpd/read_only_disk_file_system.rb",
|
156
158
|
"lib/ftpd/server.rb",
|
157
159
|
"lib/ftpd/session.rb",
|
160
|
+
"lib/ftpd/session_config.rb",
|
158
161
|
"lib/ftpd/telnet.rb",
|
159
162
|
"lib/ftpd/temp_dir.rb",
|
160
163
|
"lib/ftpd/tls_server.rb",
|
@@ -184,7 +187,7 @@ Gem::Specification.new do |s|
|
|
184
187
|
s.homepage = "http://github.com/wconrad/ftpd"
|
185
188
|
s.licenses = ["MIT"]
|
186
189
|
s.require_paths = ["lib"]
|
187
|
-
s.rubygems_version = "2.
|
190
|
+
s.rubygems_version = "2.1.3"
|
188
191
|
s.summary = "Pure Ruby FTP server library"
|
189
192
|
|
190
193
|
if s.respond_to? :specification_version then
|
data/lib/ftpd.rb
CHANGED
@@ -6,6 +6,7 @@ require 'openssl'
|
|
6
6
|
require 'pathname'
|
7
7
|
require 'shellwords'
|
8
8
|
require 'socket'
|
9
|
+
require 'strscan'
|
9
10
|
require 'thread'
|
10
11
|
require 'tmpdir'
|
11
12
|
|
@@ -31,6 +32,7 @@ module Ftpd
|
|
31
32
|
autoload :ReadOnlyDiskFileSystem, 'ftpd/read_only_disk_file_system'
|
32
33
|
autoload :Server, 'ftpd/server'
|
33
34
|
autoload :Session, 'ftpd/session'
|
35
|
+
autoload :SessionConfig, 'ftpd/session_config'
|
34
36
|
autoload :Telnet, 'ftpd/telnet'
|
35
37
|
autoload :TempDir, 'ftpd/temp_dir'
|
36
38
|
autoload :TlsServer, 'ftpd/tls_server'
|
data/lib/ftpd/ftp_server.rb
CHANGED
@@ -49,7 +49,11 @@ module Ftpd
|
|
49
49
|
#
|
50
50
|
# @return [Logger]
|
51
51
|
|
52
|
-
|
52
|
+
attr_reader :log
|
53
|
+
|
54
|
+
def log=(logger)
|
55
|
+
@log = logger || NullLogger.new
|
56
|
+
end
|
53
57
|
|
54
58
|
# The maximum number of connections the server will allow.
|
55
59
|
# Defaults to {ConnectionThrottle::DEFAULT_MAX_CONNECTIONS}.
|
@@ -62,27 +66,27 @@ module Ftpd
|
|
62
66
|
def_delegator :@connection_throttle, :'max_connections'
|
63
67
|
def_delegator :@connection_throttle, :'max_connections='
|
64
68
|
|
65
|
-
# The maximum number of
|
66
|
-
#
|
67
|
-
#
|
69
|
+
# The maximum number of failed login attempts before disconnecting
|
70
|
+
# the user. Defaults to nil (no maximum). When set, this may
|
71
|
+
# makes brute-force password guessing attack less efficient.
|
68
72
|
#
|
69
73
|
# Set this before calling #start.
|
70
74
|
#
|
71
|
-
# @!attribute max_connections_per_ip
|
72
75
|
# @return [Integer]
|
73
76
|
|
74
|
-
|
75
|
-
def_delegator :@connection_throttle, :'max_connections_per_ip='
|
77
|
+
attr_accessor :max_failed_logins
|
76
78
|
|
77
|
-
# The maximum number of
|
78
|
-
#
|
79
|
-
#
|
79
|
+
# The maximum number of connections the server will allow from a
|
80
|
+
# given IP. Defaults to
|
81
|
+
# {ConnectionThrottle::DEFAULT_MAX_CONNECTIONS_PER_IP}.
|
80
82
|
#
|
81
83
|
# Set this before calling #start.
|
82
84
|
#
|
85
|
+
# @!attribute max_connections_per_ip
|
83
86
|
# @return [Integer]
|
84
87
|
|
85
|
-
|
88
|
+
def_delegator :@connection_throttle, :'max_connections_per_ip'
|
89
|
+
def_delegator :@connection_throttle, :'max_connections_per_ip='
|
86
90
|
|
87
91
|
# The number of seconds to delay before replying. This is for
|
88
92
|
# testing, when you need to test, for example, client timeouts.
|
@@ -144,7 +148,7 @@ module Ftpd
|
|
144
148
|
@server_version = read_version_file
|
145
149
|
@allow_low_data_ports = false
|
146
150
|
@failed_login_delay = 0
|
147
|
-
|
151
|
+
self.log = nil
|
148
152
|
@connection_tracker = ConnectionTracker.new
|
149
153
|
@connection_throttle = ConnectionThrottle.new(@connection_tracker)
|
150
154
|
end
|
@@ -166,19 +170,21 @@ module Ftpd
|
|
166
170
|
end
|
167
171
|
|
168
172
|
def run_session(socket)
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
173
|
+
config = SessionConfig.new
|
174
|
+
config.allow_low_data_ports = @allow_low_data_ports
|
175
|
+
config.auth_level = @auth_level
|
176
|
+
config.driver = @driver
|
177
|
+
config.failed_login_delay = @failed_login_delay
|
178
|
+
config.list_formatter = @list_formatter
|
179
|
+
config.log = @log
|
180
|
+
config.max_failed_logins = @max_failed_logins
|
181
|
+
config.response_delay = response_delay
|
182
|
+
config.server_name = @server_name
|
183
|
+
config.server_version = @server_version
|
184
|
+
config.session_timeout = @session_timeout
|
185
|
+
config.tls = @tls
|
186
|
+
session = Session.new(config, socket)
|
187
|
+
session.run
|
182
188
|
end
|
183
189
|
|
184
190
|
def read_version_file
|
data/lib/ftpd/server.rb
CHANGED
@@ -4,7 +4,7 @@ module Ftpd
|
|
4
4
|
include Memoizer
|
5
5
|
|
6
6
|
# The interface to bind to (e.g. "127.0.0.1", "0.0.0.0",
|
7
|
-
# "10.0.0.12", etc.). Defaults to "
|
7
|
+
# "10.0.0.12", "::1", "::", etc.). Defaults to "127.0.0.1"
|
8
8
|
#
|
9
9
|
# Set this before calling #start.
|
10
10
|
#
|
@@ -23,7 +23,7 @@ module Ftpd
|
|
23
23
|
attr_accessor :port
|
24
24
|
|
25
25
|
def initialize
|
26
|
-
@interface = '
|
26
|
+
@interface = '127.0.0.1'
|
27
27
|
@port = 0
|
28
28
|
end
|
29
29
|
|
@@ -97,6 +97,7 @@ module Ftpd
|
|
97
97
|
Thread.new do
|
98
98
|
begin
|
99
99
|
session socket
|
100
|
+
rescue OpenSSL::SSL::SSLError => e
|
100
101
|
ensure
|
101
102
|
close_socket socket
|
102
103
|
end
|
data/lib/ftpd/session.rb
CHANGED
@@ -6,22 +6,13 @@ module Ftpd
|
|
6
6
|
include Error
|
7
7
|
include ListPath
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@server_version = opts[:server_version]
|
17
|
-
@driver = opts[:driver]
|
18
|
-
@auth_level = opts[:auth_level]
|
19
|
-
@socket = opts[:socket]
|
20
|
-
@tls = opts[:tls]
|
21
|
-
@list_formatter = opts[:list_formatter]
|
22
|
-
@response_delay = opts[:response_delay]
|
23
|
-
@session_timeout = opts[:session_timeout]
|
24
|
-
if @tls == :implicit
|
9
|
+
# @params session_config [SessionConfig] Session configuration
|
10
|
+
# @param socket [TCPSocket, OpenSSL::SSL::SSLSocket] The socket
|
11
|
+
|
12
|
+
def initialize(session_config, socket)
|
13
|
+
@config = session_config
|
14
|
+
@socket = socket
|
15
|
+
if @config.tls == :implicit
|
25
16
|
@socket.encrypt
|
26
17
|
end
|
27
18
|
@command_sequence_checker = init_command_sequence_checker
|
@@ -72,7 +63,7 @@ module Ftpd
|
|
72
63
|
syntax_error unless argument
|
73
64
|
sequence_error if @logged_in
|
74
65
|
@user = argument
|
75
|
-
if @auth_level > AUTH_USER
|
66
|
+
if @config.auth_level > AUTH_USER
|
76
67
|
reply "331 Password required"
|
77
68
|
expect 'pass'
|
78
69
|
else
|
@@ -83,7 +74,7 @@ module Ftpd
|
|
83
74
|
def cmd_pass(argument)
|
84
75
|
syntax_error unless argument
|
85
76
|
@password = argument
|
86
|
-
if @auth_level > AUTH_PASSWORD
|
77
|
+
if @config.auth_level > AUTH_PASSWORD
|
87
78
|
reply "332 Account required"
|
88
79
|
expect 'acct'
|
89
80
|
else
|
@@ -365,7 +356,7 @@ module Ftpd
|
|
365
356
|
end
|
366
357
|
|
367
358
|
def tls_enabled?
|
368
|
-
@tls != :off
|
359
|
+
@config.tls != :off
|
369
360
|
end
|
370
361
|
|
371
362
|
def cmd_cdup(argument)
|
@@ -617,7 +608,7 @@ module Ftpd
|
|
617
608
|
handle_data_disconnect do
|
618
609
|
data_socket.write(contents)
|
619
610
|
end
|
620
|
-
@log.debug "Sent #{contents.size} bytes"
|
611
|
+
@config.log.debug "Sent #{contents.size} bytes"
|
621
612
|
reply "226 Transfer complete"
|
622
613
|
end
|
623
614
|
end
|
@@ -628,7 +619,7 @@ module Ftpd
|
|
628
619
|
data_socket.read
|
629
620
|
end
|
630
621
|
contents = nvt_ascii_to_unix(contents) if @data_type == 'A'
|
631
|
-
@log.debug "Received #{contents.size} bytes"
|
622
|
+
@config.log.debug "Received #{contents.size} bytes"
|
632
623
|
contents
|
633
624
|
end
|
634
625
|
end
|
@@ -749,12 +740,12 @@ module Ftpd
|
|
749
740
|
s = gets_with_timeout(@socket)
|
750
741
|
throw :done if s.nil?
|
751
742
|
s = s.chomp
|
752
|
-
@log.debug s
|
743
|
+
@config.log.debug s
|
753
744
|
s
|
754
745
|
end
|
755
746
|
|
756
747
|
def gets_with_timeout(socket)
|
757
|
-
ready = IO.select([@socket], nil, nil, @session_timeout)
|
748
|
+
ready = IO.select([@socket], nil, nil, @config.session_timeout)
|
758
749
|
timeout if ready.nil?
|
759
750
|
ready[0].first.gets
|
760
751
|
end
|
@@ -765,11 +756,11 @@ module Ftpd
|
|
765
756
|
end
|
766
757
|
|
767
758
|
def reply(s)
|
768
|
-
if @response_delay.to_i != 0
|
769
|
-
@log.warn "#{@response_delay} second delay before replying"
|
770
|
-
sleep @response_delay
|
759
|
+
if @config.response_delay.to_i != 0
|
760
|
+
@config.log.warn "#{@config.response_delay} second delay before replying"
|
761
|
+
sleep @config.response_delay
|
771
762
|
end
|
772
|
-
@log.debug s
|
763
|
+
@config.log.debug s
|
773
764
|
@socket.write s + "\r\n"
|
774
765
|
end
|
775
766
|
|
@@ -807,7 +798,7 @@ module Ftpd
|
|
807
798
|
def format_list(paths)
|
808
799
|
paths.map do |path|
|
809
800
|
file_info = @file_system.file_info(path)
|
810
|
-
@list_formatter.new(file_info).to_s + "\n"
|
801
|
+
@config.list_formatter.new(file_info).to_s + "\n"
|
811
802
|
end.join
|
812
803
|
end
|
813
804
|
|
@@ -825,10 +816,10 @@ module Ftpd
|
|
825
816
|
end
|
826
817
|
|
827
818
|
def authenticate(*args)
|
828
|
-
while args.size < @driver.method(:authenticate).arity
|
819
|
+
while args.size < @config.driver.method(:authenticate).arity
|
829
820
|
args << nil
|
830
821
|
end
|
831
|
-
@driver.authenticate(*args)
|
822
|
+
@config.driver.authenticate(*args)
|
832
823
|
end
|
833
824
|
|
834
825
|
def login(*auth_tokens)
|
@@ -837,7 +828,7 @@ module Ftpd
|
|
837
828
|
error "530 Login incorrect"
|
838
829
|
end
|
839
830
|
reply "230 Logged in"
|
840
|
-
set_file_system @driver.file_system(@user)
|
831
|
+
set_file_system @config.driver.file_system(@user)
|
841
832
|
@logged_in = true
|
842
833
|
reset_failed_auths
|
843
834
|
end
|
@@ -869,8 +860,8 @@ module Ftpd
|
|
869
860
|
|
870
861
|
def failed_auth
|
871
862
|
@failed_auths += 1
|
872
|
-
sleep @failed_login_delay
|
873
|
-
if @max_failed_logins && @failed_auths >= @max_failed_logins
|
863
|
+
sleep @config.failed_login_delay
|
864
|
+
if @config.max_failed_logins && @failed_auths >= @config.max_failed_logins
|
874
865
|
reply "421 server unavailable"
|
875
866
|
throw :done
|
876
867
|
end
|
@@ -881,7 +872,7 @@ module Ftpd
|
|
881
872
|
end
|
882
873
|
|
883
874
|
def set_active_mode_address(address, port)
|
884
|
-
if port > 0xffff || port < 1024 && !@allow_low_data_ports
|
875
|
+
if port > 0xffff || port < 1024 && !@config.allow_low_data_ports
|
885
876
|
error "504 Command not implemented for that parameter"
|
886
877
|
end
|
887
878
|
@data_hostname = address
|
@@ -897,14 +888,14 @@ module Ftpd
|
|
897
888
|
@data_channel_protection_level = :clear
|
898
889
|
@data_hostname = nil
|
899
890
|
@data_port = nil
|
900
|
-
@protection_buffer_size_set =
|
891
|
+
@protection_buffer_size_set = false
|
901
892
|
@epsv_all = false
|
902
893
|
close_data_server_socket
|
903
894
|
reset_failed_auths
|
904
895
|
end
|
905
896
|
|
906
897
|
def server_name_and_version
|
907
|
-
"#{@server_name} #{@server_version}"
|
898
|
+
"#{@config.server_name} #{@config.server_version}"
|
908
899
|
end
|
909
900
|
|
910
901
|
end
|
@@ -0,0 +1,103 @@
|
|
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
|
+
end
|
102
|
+
|
103
|
+
end
|
data/lib/ftpd/telnet.rb
CHANGED
@@ -50,7 +50,7 @@ module Ftpd
|
|
50
50
|
# @param command [String]
|
51
51
|
|
52
52
|
def initialize(command)
|
53
|
-
|
53
|
+
parse_command command
|
54
54
|
end
|
55
55
|
|
56
56
|
private
|
@@ -66,51 +66,46 @@ module Ftpd
|
|
66
66
|
end
|
67
67
|
include Codes
|
68
68
|
|
69
|
-
def
|
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)
|
70
100
|
@plain = ''
|
71
101
|
@reply = ''
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
else
|
79
|
-
@plain << c
|
80
|
-
end
|
81
|
-
when :iac
|
82
|
-
case c
|
83
|
-
when IAC
|
84
|
-
@plain << c
|
85
|
-
state = :idle
|
86
|
-
when WILL
|
87
|
-
state = :will
|
88
|
-
when WONT
|
89
|
-
state = :wont
|
90
|
-
when DO
|
91
|
-
state = :do
|
92
|
-
when DONT
|
93
|
-
state = :dont
|
94
|
-
when IP
|
95
|
-
state = :idle
|
96
|
-
when DM
|
97
|
-
state = :idle
|
98
|
-
else
|
99
|
-
@plain << IAC + c
|
100
|
-
state = :idle
|
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
|
101
108
|
end
|
102
|
-
when :will
|
103
|
-
@reply << IAC + DONT + c
|
104
|
-
state = :idle
|
105
|
-
when :wont
|
106
|
-
state = :idle
|
107
|
-
when :do
|
108
|
-
@reply << IAC + WONT + c
|
109
|
-
state = :idle
|
110
|
-
when :dont
|
111
|
-
state = :idle
|
112
|
-
else
|
113
|
-
raise "Unknown state #{state.inspect}"
|
114
109
|
end
|
115
110
|
end
|
116
111
|
end
|
data/rake_tasks/jeweler.rake
CHANGED
@@ -6,7 +6,10 @@ README_PATH = File.expand_path('../README.md', File.dirname(__FILE__))
|
|
6
6
|
|
7
7
|
def extract_description_from_readme
|
8
8
|
readme = File.open(README_PATH, 'r', &:read)
|
9
|
-
s = readme[/^# FTPD
|
9
|
+
s = readme[/^# FTPD.*\n+((?:.*\n)+?)\n*##/i, 1]
|
10
|
+
unless s
|
11
|
+
raise 'Unable to extract description from readme'
|
12
|
+
end
|
10
13
|
s.gsub(/\n/, ' ').strip
|
11
14
|
end
|
12
15
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ftpd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Wayne Conrad
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-10-
|
11
|
+
date: 2013-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: memoizer
|
@@ -138,7 +138,7 @@ dependencies:
|
|
138
138
|
version: '0'
|
139
139
|
description: ftpd is a pure Ruby FTP server library. It supports implicit and explicit
|
140
140
|
TLS, passive and active mode, and is unconditionally compliant per [RFC-1123][1]. It
|
141
|
-
|
141
|
+
can be used as part of a test fixture or embedded in a program.
|
142
142
|
email: wconrad@yagni.com
|
143
143
|
executables: []
|
144
144
|
extensions: []
|
@@ -146,6 +146,7 @@ extra_rdoc_files:
|
|
146
146
|
- LICENSE.md
|
147
147
|
- README.md
|
148
148
|
files:
|
149
|
+
- .travis.yml
|
149
150
|
- .yardopts
|
150
151
|
- Changelog.md
|
151
152
|
- Gemfile
|
@@ -284,6 +285,7 @@ files:
|
|
284
285
|
- lib/ftpd/read_only_disk_file_system.rb
|
285
286
|
- lib/ftpd/server.rb
|
286
287
|
- lib/ftpd/session.rb
|
288
|
+
- lib/ftpd/session_config.rb
|
287
289
|
- lib/ftpd/telnet.rb
|
288
290
|
- lib/ftpd/temp_dir.rb
|
289
291
|
- lib/ftpd/tls_server.rb
|
@@ -329,7 +331,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
329
331
|
version: '0'
|
330
332
|
requirements: []
|
331
333
|
rubyforge_project:
|
332
|
-
rubygems_version: 2.
|
334
|
+
rubygems_version: 2.1.3
|
333
335
|
signing_key:
|
334
336
|
specification_version: 4
|
335
337
|
summary: Pure Ruby FTP server library
|