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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 402da43ab955cc1f7ec47d5080556ae09c85da9d
4
- data.tar.gz: 6cde23e0be18f61b46e544809127337623ee55e9
3
+ metadata.gz: 1c547a0bf777999094a612c387fb63d40a2cbc7f
4
+ data.tar.gz: 021d0b2bdad238406fdc19d00c9b8036ec85a749
5
5
  SHA512:
6
- metadata.gz: 2a434cd8438916a37900e2e6dc37a56c9b87d0628ed979f18e9ed8e1bba4f4a31252c012de9bfaa77b7078440212231ae1b073a848a5d80f5f43819277df0399
7
- data.tar.gz: a9a3d3875c56cb3d8f5ba701291e9c2c22668b3d42534cdb34eb79fc81f781e49b00c42eb8cab8b5a13f67e47e2b846ddb93e7f8faf38086b4b14205a10118f1
6
+ metadata.gz: 5b7560963a406e9098cbbff5ba71741c642c93ce7f6296218eb28e69027c99837b07891abf922111e84601792871c3296211f66b8865711520b7a0bdc8ac3663
7
+ data.tar.gz: 5797b248427a0d497de07918094c35db4a921ae016e016ae63f48fef901c8ba4a38064b0314e62037c00dd695f879bf6b0e880441f9701d7ad884328838f5331
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
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.19)
27
+ highline (1.6.20)
28
28
  httpauth (0.2.0)
29
- jeweler (1.8.7)
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.0)
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.0)
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 an be used as part of a test fixture or
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, and other files, contains Yardoc markup, especially for
11
- links to the API docs; those links don't display properly on github.
12
- You'll find a properly rendered version [on
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 Logger} that you provide. To log to standard out:
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.9.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: ftp://#{connection_host}:#{@server.bound_port}"
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|
@@ -1,4 +1,4 @@
1
- Feature: Port
1
+ Feature: ALLO
2
2
 
3
3
  As a client
4
4
  I want to reserve file system space
@@ -5,6 +5,7 @@ Feature: EPRT
5
5
  So that I can correct problems
6
6
 
7
7
  Background:
8
+ Given the test server is bound to "::"
8
9
  Given the test server is started
9
10
 
10
11
  Scenario: Port 1024
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.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-01"
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 an be used as part of a test fixture or embedded in a program."
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.0.0"
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'
@@ -49,7 +49,11 @@ module Ftpd
49
49
  #
50
50
  # @return [Logger]
51
51
 
52
- attr_accessor :log
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 connections the server will allow from a
66
- # given IP. Defaults to
67
- # {ConnectionThrottle::DEFAULT_MAX_CONNECTIONS_PER_IP}.
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
- def_delegator :@connection_throttle, :'max_connections_per_ip'
75
- def_delegator :@connection_throttle, :'max_connections_per_ip='
77
+ attr_accessor :max_failed_logins
76
78
 
77
- # The maximum number of failed login attempts before disconnecting
78
- # the user. Defaults to nil (no maximum). When set, this may
79
- # makes brute-force password guessing attack less efficient.
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
- attr_accessor :max_failed_logins
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
- @log = nil
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
- Session.new(:allow_low_data_ports => allow_low_data_ports,
170
- :auth_level => @auth_level,
171
- :driver => @driver,
172
- :failed_login_delay => @failed_login_delay,
173
- :list_formatter => @list_formatter,
174
- :log => log,
175
- :max_failed_logins => @max_failed_logins,
176
- :response_delay => response_delay,
177
- :server_name => @server_name,
178
- :server_version => @server_version,
179
- :session_timeout => @session_timeout,
180
- :socket => socket,
181
- :tls => @tls).run
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 "localhost"
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 = 'localhost'
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
- def initialize(opts)
10
- @failed_login_delay = opts[:failed_login_delay]
11
- @connection_tracker = opts[:connection_tracker]
12
- @max_failed_logins = opts[:max_failed_logins]
13
- @log = opts[:log] || NullLogger.new
14
- @allow_low_data_ports = opts[:allow_low_data_ports]
15
- @server_name = opts[:server_name]
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 = 0
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
- telnet_state_machine command
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 telnet_state_machine (command)
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
- state = :idle
73
- command.each_char do |c|
74
- case state
75
- when :idle
76
- if c == IAC
77
- state = :iac
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
@@ -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\n+((?:.*\n)+?)\n*##/i, 1]
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.9.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-01 00:00:00.000000000 Z
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
- an be used as part of a test fixture or embedded in a program.
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.0.0
334
+ rubygems_version: 2.1.3
333
335
  signing_key:
334
336
  specification_version: 4
335
337
  summary: Pure Ruby FTP server library