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 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