ftpd 0.9.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Code Climate](https://codeclimate.com/github/wconrad/ftpd.png)](https://codeclimate.com/github/wconrad/ftpd) [![Build Status](https://travis-ci.org/wconrad/ftpd.png)](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
|