Git Product home page Git Product logo

ftpd's People

Contributors

akelmanson avatar alfius avatar bsodmike avatar iblue avatar j1wilmot avatar kingsabri avatar ragalie avatar ukolovda avatar wconrad avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

ftpd's Issues

[IMPROVEMENT] Cmd Size and exotic filesystem

The command SIZE is calling read method of the file driver. Which is bad in the case your driver is not a file system.

For example, I ftpd with google bucket and it works perfectly except for this command.

For informations, we monkey-patched the method internally for our tool, like that:

( PS: I'm not gonna open a pull request as it's not just debugging feature, and better idea of implementation would exists )

# frozen_string_literal: true

require_relative 'command_handler'

module Ftpd

  class CmdSize < CommandHandler

    def cmd_size(path)
      ensure_logged_in
      ensure_file_system_supports :read
      syntax_error unless path
      path = File.expand_path(path, name_prefix)
      ensure_accessible(path)
      ensure_exists(path)
      if file_system.respond_to?(:size)
        size = file_system.size(path, data_type).to_i
        reply "213 #{size}"
      else
        file_system.read(path) do |file|
          if data_type == 'A'
            output = StringIO.new
            io = Ftpd::Stream.new(output, data_type)
            io.write(file)
            size = output.size
          else
            size = file.size
          end
          reply "213 #{size}"
        end

      end

    end

  end

end

Feature request: callbacks

Hello,
is it possible to run ftp commands (e.g CWD and setting RootDir) right after the user is authenticated ?
i want to create a policy based Service .
Thanks

CWD responds with non RFC959 compliant code

I was debugging an FTP issue and my client was refusing to upload a file. Looking at the debug logs, I saw this:

D, [2014-01-26T12:59:38.398677 #43939] DEBUG -- : 220 wconrad/ftpd 0.10.0
D, [2014-01-26T12:59:38.402758 #43939] DEBUG -- : USER neteng
D, [2014-01-26T12:59:38.403115 #43939] DEBUG -- : 230 Logged in
D, [2014-01-26T12:59:38.406421 #43939] DEBUG -- : TYPE I
D, [2014-01-26T12:59:38.406624 #43939] DEBUG -- : 200 Type set to I
D, [2014-01-26T12:59:38.410524 #43939] DEBUG -- : CWD .
D, [2014-01-26T12:59:38.410723 #43939] DEBUG -- : 257 "/" is current directory

The client wasn't expecting that 257 and errored on... success. Looking at RFC959, it shows that valid codes are:

               CWD
                  250
                  500, 501, 502, 421, 530, 550

The last line of cmd_cwd calls pwd, which, I believe should just be changed to a 250 response.

I'm happy to patch and submit a pull request if you agree.

Add more handlers

I found that my application sometimes stopped working.

I found that sometimes can be issue on opening TcpSocket on transfer:

ruby/2.1.0/gems/ftpd-1.0.1/lib/ftpd/data_connection_helper.rb:62:in `initialize': Connection timed out - connect(2) for "10.89.***" port 59602 (Errno::ETIMEDOUT)

rake test:features seems to be broken

If I use Ruby 2.4.1 and install using rvm, I get this error message when trying to run tests with rake:

rvm use 2.4.1
gem install bundler
bundle install

rake test:features
rake aborted!
NoMethodError: undefined method `last_comment' for #<Rake::Application:0x007fe8f6843ac8>
/Volumes/Users/beard/.rvm/gems/ruby-2.4.1/gems/yard-0.8.7.6/lib/yard/rake/yardoc_task.rb:69:in `define'
/Volumes/Users/beard/.rvm/gems/ruby-2.4.1/gems/yard-0.8.7.6/lib/yard/rake/yardoc_task.rb:61:in `initialize'
rake_tasks/yard.rake:4:in `new'
rake_tasks/yard.rake:4:in `<top (required)>'
/Volumes/Users/beard/Trees/ftpd/Rakefile:14:in `load'
/Volumes/Users/beard/Trees/ftpd/Rakefile:14:in `block in <top (required)>'
/Volumes/Users/beard/Trees/ftpd/Rakefile:14:in `each'
/Volumes/Users/beard/Trees/ftpd/Rakefile:14:in `<top (required)>'
/Volumes/Users/beard/.rvm/gems/ruby-2.4.1@global/gems/rake-12.0.0/exe/rake:27:in `<top (required)>'
/Volumes/Users/beard/.rvm/gems/ruby-2.4.1/bin/ruby_executable_hooks:15:in `eval'
/Volumes/Users/beard/.rvm/gems/ruby-2.4.1/bin/ruby_executable_hooks:15:in `<main>'
(See full trace by running task with --trace)

I suspect it was the change that bumped rake to 12.0.0. Backing it out like this:

diff --git a/Gemfile b/Gemfile
index 42ffe63..d2a15b6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,7 +3,7 @@ source 'http://rubygems.org'
 gem 'memoizer', '~> 1.0'
 
 group :test, :development do
-  gem 'rake', '~> 12.0'
+  gem 'rake', '~> 11.1'
 end
 
 group :test do
diff --git a/Gemfile.lock b/Gemfile.lock
index 75d8174..3a17115 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -62,7 +62,7 @@ GEM
     psych (2.2.4)
     public_suffix (2.0.5)
     rack (2.0.2)
-    rake (12.0.0)
+    rake (11.1.0)
     rdoc (5.1.0)
     redcarpet (3.4.0)
     rspec (3.6.0)
@@ -94,7 +94,7 @@ DEPENDENCIES
   double-bag-ftps (~> 0.1, >= 0.1.3)
   jeweler (~> 2.0)
   memoizer (~> 1.0)
-  rake (~> 12.0)
+  rake (~> 11.1)
   redcarpet (~> 3.1)
   rspec (~> 3.1)
   rspec-its (~> 1.0)

Then reinstalling rake like this:

gem uninstall rake
bundle install

Seems to restore testing functionality.

FTPPermError 550 Access Denied.

I have a test that starts the server and then i create a connection to the server. I`m able to list the folder but when i make a put operation it fails with access denied error.
how did you used the server so you can put , and mdir using a FTP connection .?

Connection Hangs on "ls"

My Driver:

class Driver
  def self.authenticate(user, password)
    true
  end
  def self.file_system(user)
    path = "/tmp/backup/"
    Ftpd::DiskFileSystem.new(path.to_s)
  end 
end

Definitions

@server = Ftpd::FtpServer.new(Driver)
@server.session_timeout = 10 * 60

Start the server

@server.interface = @localip
@server.start
 puts "FTP Server listening on port #{@server.bound_port}, Press enter to close the FTP server\n"
gets
@server.stop

My issue

When I connect I can send commands like "pwd" etc.. but when I send "ls" this is what happens

230 Logged in
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection

and it hangs forever until I press Ctrl+x, on the server side it crashes with this error
screenshot at 14-14-55

#stop causes "Errno::ENOTCONN: Socket is not connected" in OSX

@jcarlson reports that the bug fix for #23 causes "Errno::ENOTCONN: Socket is not connected" in OSX when #stop is called on the server:


Unfortunately, this doesn't fix my problem. Now, on OS X, when I call server.stop, it raises an error immediately:

[6] pry(main)> server.stop
Errno::ENOTCONN: Socket is not connected
from /Users/dev/.rvm/gems/ruby-2.0.0-p353@corndog/gems/ftpd-0.14.0/lib/ftpd/server.rb:53:in `shutdown'

This is better because it fails fast, but obviously still an issue.

I'm on Ruby 2.0.0p353 on OS X 10.8.5. Our CI server is on Ubuntu.

FWIW, the reason I don't use a random port number is that my client code reads settings from a YAML file in the config directory. So when I setup my test server, I also read from that YAML file and have the server start on the port that the YAML file specifies. I guess I could hack around until I can make the port a sort of shared read/write global that the server can update when the port is known... but I don't particularly love that path.


Here's a couple test helper methods that seem to get the trick done with v0.11.0:

def start
  @thread = @server.start
  @running = true
end

def stop
  if @running
    @server.stop
    @thread.exit
  end
  @running = false
end

Error Connection reset by peer @ io_fillbuf - fd:158

Hi, I wonder if you could please help as I'm encountering issues with this gem in a live project and I'm at a bit of a loss as to what is happening so if anyone can provide any help it would be greately appreciated!

Overview

The FTP integration is used by a number of old weather stations that connect via FTP, over a mobile network, and write 1 line of text every minute. I'm simply using an override for the write_file() function that looks at the filename and then passed the line to be written on to be written to the database. This was working just fine for months until recently when I started to see 'Error Connection reset by peer @ io_fillbuf - fd:{number}' errors. Once these start to appear in the logs, slowly all of the connections start to generate this error until the FTP server refuses to accept connections and the only way that seems to fix it so far is to restart the application, where it will run for a apparently random amount of time from 7 hours to over a day before this starts to happen again, requiring another restart.

System

  • Debian docker instance Debian GNU/Linux 9.13 (stretch)
  • ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
  • ftpd-2.1.0

What's unusual and did anything change?

  1. More weather stations were added but the total is only 28.
  2. These stations are using mobile network and are kind of slow. Mostly the session is < 1 second from connect to write and close but this can be up to 9 seconds from what I've seen.
  3. Apart from the weather stations connecting in, there's one connection from someone who is connecting in every 5 minutes to read the files for their own purposes, which is some legacy task from a 3rd part system.
  4. There is a miss-configured weather station that tries to connect every minute with the wrong credentials.
  5. There does not appear to be a consistent time frame between restart and the first instance of the error in the logs. Complete lockout (all connections refused) is almost daily though.

What are the settings?

It may be worth mentioning that this is running in a docker environment with mapped ports

The file system uses the default.

Initializers

require 'driver'

# Need to overwrite the write function so we can grab the data
Ftpd::DiskFileSystem.class_eval do
  
  def write_file(ftp_path, stream, mode)
    begin

      File.open(expand_ftp_path(ftp_path), mode) do |file|

        # Get the details from the path
        arr_acc_dh = file.path.gsub(FTP_ROOT, "").split("/")
        account_id = arr_acc_dh[1]
        datahog_id = arr_acc_dh[2]

        dh_importer = DataImporter.new()
        while line = stream.read

          # Ignore anything other than day data
          if ftp_path.include? "daydata.txt"
            dh_importer.import_data(line, account_id, datahog_id)
          elsif ftp_path.include? "rssidata.txt"
            dh_importer.import_ssri_data(line, account_id, datahog_id)
          end  
          file.write line
        end
      end
    
    rescue Exception => e

      # Something bad happened
      puts "ERROR : #{DateTime.now} : #{Thread.current.object_id} : write_file:  #{e.full_message} \n"      
    end
  end

end


# Override to set host and port 
Ftpd::Session.class_eval do

  def initialize_session
    @logged_in = false
    @data_type = 'A'
    @mode = 'S'
    @structure = 'F'
    @data_channel_protection_level = :clear
    @name_prefix = '/'
    # @data_hostname = nil
    # @data_port = nil
    @data_hostname = '{hostname}'
    @data_port = 2157 
    @protection_buffer_size_set = false
    @epsv_all = false
    close_data_server_socket
    reset_failed_auths
  end

end 

# Strange error on stat request on live server. Debug attempt
Ftpd::CmdStat.class_eval do

  def cmd_stat(argument)
    reply "220 sd/ftpd 2.1.0"
  end

end

Main execution block

# Set the log output and override to include thread info
skye_logger = Logger.new($stdout) #Logger.new('/var/log/ftpd.log')
skye_logger.formatter = proc do |severity, datetime, progname, msg|
   "#{severity} : #{datetime} : #{Thread.current.object_id} : #{msg}\n"
end

driver = Driver.new("/var/www/shared/ftp_storage" , skye_logger) # Pass logger to driver 
server = Ftpd::FtpServer.new(driver)
server.log = skye_logger
server.port = 2157
server.interface = '0.0.0.0'
server.nat_ip = '{External Server IP}'
server.passive_ports = Range.new(4021, 4060)
server.allow_low_data_ports = false
server.session_timeout = 45
server.failed_login_delay = 120
server.max_connections = 100
server.max_connections_per_ip = 1
server.tls = :off

server.start
puts "FTP Server listening on port #{server.bound_port}"
puts "FTP Server interface #{server.interface}"
begin
  gets
rescue Interrupt
  puts "Interrupt"
end`

Driver Code

require 'ftpd'
require "tmpdir"

class Driver

  def initialize(ftp_root, logger)
    @logger = logger
    @ftp_root = ftp_root
    @full_datahog_data_path = ''
    @logger.debug "passed ftp_root #{ftp_root}"
  end

  def authenticate(user, password)
    begin
      
      # Expect the username to be consisting of 2 numbers account_id and hog_id. 
      account_id = ''
      datahog_id = ''
      if user.split("_").count != 2
        raise "invalid login. Must consist of account_id and datahog_id e.g. '123_456'"
      end  
      
      account_id = user.split("_")[0].to_i
      datahog_id = user.split("_")[1].to_i

      datahog = Datahog.includes(:account).references(:account).where(id: datahog_id).where({account: { id: account_id}}).where({datahogs: { ftp_password: password}}).where(account: {active: true})
    
      if datahog.blank?
        @logger.info "Invalid FTP credentials or inactive account. No matching datahog found for user: #{user} and password: #{password}"
        false
      else
      
        # Create the account directory if it doesn't exist already
        ftp_root_with_account = "#{@ftp_root}/#{account_id}"      

        unless File.exist?(ftp_root_with_account)
          FileUtils.mkdir_p(ftp_root_with_account)
        end

        # Now the datahog directory
        self.instance_variable_set(:@full_datahog_path, "#{ftp_root_with_account}/#{datahog_id}")

        unless File.exist?(self.instance_variable_get(:@full_datahog_path))
          FileUtils.mkdir_p(self.instance_variable_get(:@full_datahog_path))
        end 

        self.instance_variable_set(:@full_datahog_data_path, "#{self.instance_variable_get(:@full_datahog_path)}/data")

        unless File.exist?(self.instance_variable_get(:@full_datahog_data_path))
         FileUtils.mkdir_p(self.instance_variable_get(:@full_datahog_data_path))
        end

        @logger.debug "User '#{user}'. instance var full_datahog_data_path : '#{self.instance_variable_get(:@full_datahog_data_path)}'"

        true  
      end 
      
    rescue Exception => ex
      @logger.error "FTP driver - Authentication failure #{ex.full_message}"
      false
    end
  end

  def file_system(user)
    Ftpd::DiskFileSystem.new(self.instance_variable_get(:@full_datahog_path))
  end

end

Modification to get full all thread stack trace

CommandLoop modified with this function

def read_and_execute_commands
      catch :done do
        begin
          reply "220 #{server_name_and_version}"
          loop do
            begin
              s = get_command
              s = process_telnet_sequences(s)
              syntax_error unless s =~ /^(\w+)(?: (.*))?$/
              command, argument = $1.downcase, $2
              unless valid_command?(command)
                error "Syntax error, command unrecognized: #{s.chomp}", 500
              end
              command_sequence_checker.check command
              execute_command command, argument
            rescue FtpServerError => e
              reply e.message_with_code
            rescue => e

              # Get the full back trace
              puts "Error thread #{Thread.current.object_id}. Local error in processing. Error #{e.message}"
              thread_count = 0
              err_msg = ""
              Thread.list.each do |t|
                thread_count += 1
                err_msg += "--- thread #{thread_count} of total #{Thread.list.size}. Thread.id #{t.object_id} backtrace begin \n"
                # Lets see if we are able to pin down the culprit
                # by collecting backtrace for all existing threads:
                err_msg += t.backtrace.join("\n")
                err_msg += "\n---thread #{thread_count} of total #{Thread.list.size}. Thread.id #{t.object_id} backtrace end \n"
              end
              puts "Full thread & object backtrace : - \n #{err_msg}"

              reply "451 Requested action aborted. Local error in processing. Error #{e.message}"
              config.exception_handler.call(e) unless config.exception_handler.nil?

            end
          end
        rescue Errno::ECONNRESET, Errno::EPIPE
        end
      end
    end

Working example weather station transfer

DEBUG : 2022-09-22 14:24:45 +0100 : 429400 : 220 SmartData_Skye 2.1.0
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : USER 19_51
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : 331 Password required
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : PASS **FILTERED**
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : User '19_51'. instance var full_datahog_data_path : '/var/www/shared/ftp_storage/19/51/data'
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : 230 Logged in
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : PASV
DEBUG : 2022-09-22 14:24:46 +0100 : 429400 : 227 Entering passive mode (212,110,172,113,15,234)
DEBUG : 2022-09-22 14:24:47 +0100 : 429400 : TYPE I
DEBUG : 2022-09-22 14:24:47 +0100 : 429400 : 200 Type set to I
DEBUG : 2022-09-22 14:24:47 +0100 : 429400 : CWD data
DEBUG : 2022-09-22 14:24:47 +0100 : 429400 : 250 "/data" is current directory
DEBUG : 2022-09-22 14:24:48 +0100 : 429400 : APPE 220922daydata.txt
DEBUG : 2022-09-22 14:24:48 +0100 : 429400 : 150 Opening BINARY mode data connection
DEBUG : 2022-09-22 14:24:51 +0100 : 429400 : import_data.account_id : 19 import_data.datahog_id : 51
DEBUG : 2022-09-22 14:24:51 +0100 : 429400 : 13:17:00  22.09.22   00  0425.28  01  018.69   02  004.462  03  00210    04  0000.00  05  01.4573  06  019.52   07  055.105
DEBUG : 2022-09-22 14:24:51 +0100 : 429400 : Date time found: '22.09.22 13:17:00'. Format to use '%d.%m.%y %H:%M:%S'
DEBUG : 2022-09-22 14:24:51 +0100 : 429400 : Received 126 bytes
DEBUG : 2022-09-22 14:24:51 +0100 : 429400 : 226 Transfer complete

Note: Above is the most common scenario in that I don't always see the extra quit line...

DEBUG : 2022-09-22 14:19:11 +0100 : 419900 : QUIT
DEBUG : 2022-09-22 14:19:11 +0100 : 419900 : 221 Byebye

...This is why I set the session timeout low in case connections aren't being closed.

Example of the odd one out
This connection is not made by the weather stations but by a 3rd party FTP request for their own purposes

DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 220 SmartData_Skye 2.1.0
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : USER 17_49
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 331 Password required
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : PASS **FILTERED**
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : User '17_49'. instance var full_datahog_data_path : '/var/www/shared/ftp_storage/17/49/data'
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 230 Logged in
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : FEAT
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 211-Extensions supported:
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 :  EPRT
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 :  EPSV
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 :  MDTM
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 :  SIZE
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 211 END
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : TYPE I
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 200 Type set to I
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : CWD data
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 250 "/data" is current directory
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : EPSV
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 229 Entering extended passive mode (|||4054|)
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : RETR 220922daydata.txt
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 150 Opening BINARY mode data connection
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : Sent 12789 bytes
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 226 Transfer complete
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : MODE S
DEBUG : 2022-09-22 14:36:07 +0100 : 446940 : 200 Mode set to Strea

Full stack trace on io_fillbuf error including all threads

Included log from the start of thread id 2753380 : -

20220922_full_stack_trace.txt

Using ruby 2.4 fails

I tried installing the most modern ruby available in Homebrew (ruby 2.4), and kept running into a problem compiling json (1.8.3):

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /usr/local/lib/ruby/gems/2.4.0/gems/json-1.8.3/ext/json/ext/generator
/usr/local/opt/ruby/bin/ruby -r ./siteconf20170624-88439-qnl4kq.rb extconf.rb
creating Makefile

current directory: /usr/local/lib/ruby/gems/2.4.0/gems/json-1.8.3/ext/json/ext/generator
make "DESTDIR=" clean

current directory: /usr/local/lib/ruby/gems/2.4.0/gems/json-1.8.3/ext/json/ext/generator
make "DESTDIR="
compiling generator.c
generator.c:861:25: error: use of undeclared identifier 'rb_cFixnum'
    } else if (klass == rb_cFixnum) {

Easily fixed by editing Gemfile.lock to use the latest json (2.1.0):

diff --git a/Gemfile.lock b/Gemfile.lock
index d1313a4..81b7532 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -42,7 +42,7 @@ GEM
       rake
       rdoc
       semver
-    json (1.8.3)
+    json (2.1.0)
     jwt (1.5.6)
     memoizer (1.0.1)
     mini_portile2 (2.1.0)

Issue with TLS protocol

Since update of our client with the latest version of gnuTLS, the access to our implementation of FTP with ftpd is not working anymore:

GnuTLS error -58: An illegal TLS extension was received

I think the problem comes from the usage of a non-supported-anymore encryption system, but I wonder if the problem lay down into the code of ftpd or in our private/public key pair?

Stack trace after failed SSL connection

If ftpd is configured for implicit SSL, then when a client connects and immediately disconnects, the server dies with a stack trace.

To reproduce, start the server:

$ examples/example.rb --tls=implicit

Then connect using telnet, giving the port number that the server advertised when it started. Use ctrl-] to enter the telnet client's command mode, then enter "q" to quit. This disconnects the client from the server.

$ telnet localhost 47802
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
^]
telnet> q

The server dies with a stack trace:

/home/wayne/lab/ftpd/lib/ftpd/tls_server.rb:79:in `accept': SSL_accept SYSCALL returned=5 errno=0 state=unknown state (OpenSSL::SSL::SSLError)
        from /home/wayne/lab/ftpd/lib/ftpd/tls_server.rb:79:in `encrypt'
        from /home/wayne/lab/ftpd/lib/ftpd/session.rb:24:in `initialize'
        from /home/wayne/lab/ftpd/lib/ftpd/ftp_server.rb:169:in `new'
        from /home/wayne/lab/ftpd/lib/ftpd/ftp_server.rb:169:in `run_session'
        from /home/wayne/lab/ftpd/lib/ftpd/ftp_server.rb:164:in `block in session'
        from /home/wayne/lab/ftpd/lib/ftpd/connection_tracker.rb:36:in `track'
        from /home/wayne/lab/ftpd/lib/ftpd/ftp_server.rb:163:in `session'
        from /home/wayne/lab/ftpd/lib/ftpd/server.rb:99:in `block in start_session_thread'

Unable to reuse same port after stopping server

In my rspec suite, I am starting/stopping the server between examples and setting the port to a known value each time.

The first spec works, but subsequent specs fail, citing the following error:
An error occurred in an after hook
NoMethodError: undefined method close' for nil:NilClass occurred at /home/ubuntu/Corndog/vendor/bundle/ruby/2.0.0/gems/ftpd-0.11.0/lib/ftpd/server.rb:53:instop'
...
Errno::EADDRINUSE: Address already in use - bind(2)

For each spec, I am creating a new server instance in the before block and starting the server. I am stopping the server in the after block.

From putzing around in the rails console, it seems like this works on OSX but not on Ubuntu.

As far as I can tell, when asking the server to stop, the associated thread does not stop listening to the bound port.

I think I can work around the issue by saving a handle to the Thread returned by server.start and then killing that thread manually when I stop my server, but it seems like maybe this is an issue with Ftpd?

ftpd windows version , the ftp server will crash if input 'dir'

E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/disk_file_system.rb:311:i
n gid_name': undefined method name' for nil:NilClass (NoMethodError)
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/disk_file_sy
stem.rb:257:in file_info' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/translate_ex ceptions.rb:45:in call'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/translate_ex
ceptions.rb:45:in block (2 levels) in translate_exceptions' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/exception_tr anslator.rb:24:in translate_exceptions'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/translate_ex
ceptions.rb:44:in block in translate_exceptions' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/file_system_ error_translator.rb:19:in method_missing'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:4
04:in block in format_list' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:4 03:in map'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:4
03:in format_list' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:3 99:in list'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/cmd_list.rb:
12:in block in cmd_list' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:3 14:in close_data_server_socket_when_done'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/cmd_list.rb:
6:in cmd_list' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/command_hand lers.rb:41:in execute'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:1
14:in execute_command' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:5 1:in block (2 levels) in run'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:4
1:in loop' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:4 1:in block in run'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:3
8:in catch' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/session.rb:3 8:in run'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/ftp_server.r
b:185:in run_session' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/ftp_server.r b:166:in block in session'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/connection_t
racker.rb:36:in track' from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/ftp_server.r b:165:in session'
from E:/Ruby22/lib/ruby/gems/2.2.0/gems/ftpd-2.0.0/lib/ftpd/server.rb:10
5:in `block in start_session_thread'

Tests fail under Ruby >= 2.2.3

Due to an issue with Ruby's ftp-tls client, double-bag-ftps, this unit test hangs with ruby-2.3.0:

$ bundle exec cucumber --format pretty -b features/ftp_server/get_tls.feature:7
Feature: Get TLS
  As a client
  I want to get a file
  So that I have it on my computer

  Background:                                     # features/ftp_server/get_tls.feature:7
    Given the test server has TLS mode "explicit" # features/ftp_server/step_definitions/test_server.rb:9
    And the test server is started                # features/ftp_server/step_definitions/test_server.rb:5

  Scenario: Active                                                # features/ftp_server/get_tls.feature:11
    Given a successful login with explicit TLS                    # features/step_definitions/login.rb:16
    And the server has file "ascii_unix"                          # features/step_definitions/server_files.rb:7
    And the client is in active mode                              # features/step_definitions/passive.rb:1
(hung)

There's a PR for double-bag-ftps that will make this test pass: bnix/double-bag-ftps#12

connections prematurely closing (osx?)

Hi, I'm playing with ftpd. Right now on osx, though this will eventually be deployed to a linux machine.

I'm not sure what the expected behavior is, but I'm seeing the server exit with a stacktrace when ever a client connection is closed.

Using the example:

congress:ftpd seph$ ./examples/hello_world.rb 
Server listening on port 62829
/private/tmp/ftpd/lib/ftpd/server.rb:112:in `shutdown': Socket is not connected (Errno::ENOTCONN)
    from /private/tmp/ftpd/lib/ftpd/server.rb:112:in `close_socket'
    from /private/tmp/ftpd/lib/ftpd/server.rb:101:in `block in start_session_thread'

And the tests:

congress:ftpd seph$ FTPD_DEBUG=1 rake test
/Users/seph/.rbenv/versions/1.9.3-p327/bin/ruby -S rspec ./spec/command_sequence_checker_spec.rb ./spec/connection_throttle_spec.rb ./spec/connection_tracker_spec.rb ./spec/disk_file_system_spec.rb ./spec/exception_translator_spec.rb ./spec/file_info_spec.rb ./spec/file_system_error_translator_spec.rb ./spec/list_format/eplf_spec.rb ./spec/list_format/ls_spec.rb ./spec/null_logger_spec.rb ./spec/telnet_spec.rb ./spec/translate_exceptions_spec.rb
.......................................................................................................................................................

Finished in 0.12331 seconds
151 examples, 0 failures
/Users/seph/.rbenv/versions/1.9.3-p327/bin/ruby -S bundle exec cucumber --format progress
..........................................................................D, [2013-04-09T16:32:09.113970 #65819] DEBUG -- : 220 wconrad/ftpd 0.6.0
.D, [2013-04-09T16:32:09.116057 #65819] DEBUG -- : ABOR
D, [2013-04-09T16:32:09.117144 #65819] DEBUG -- : 502 Command not implemented
....D, [2013-04-09T16:32:09.124672 #65819] DEBUG -- : 220 wconrad/ftpd 0.6.0
D, [2013-04-09T16:32:09.125222 #65819] DEBUG -- : USER user
D, [2013-04-09T16:32:09.125304 #65819] DEBUG -- : 331 Password required
D, [2013-04-09T16:32:09.125449 #65819] DEBUG -- : PASS password
D, [2013-04-09T16:32:09.125546 #65819] DEBUG -- : 230 Logged in
D, [2013-04-09T16:32:09.129515 #65819] DEBUG -- : TYPE I
D, [2013-04-09T16:32:09.129605 #65819] DEBUG -- : 200 Type set to I
.D, [2013-04-09T16:32:09.131564 #65819] DEBUG -- : ALLO 1024
D, [2013-04-09T16:32:09.131648 #65819] DEBUG -- : 202 Command not needed at this site
...F--.D, [2013-04-09T16:32:09.153627 #65819] DEBUG -- : 220 wconrad/ftpd 0.6.0
.D, [2013-04-09T16:32:09.156744 #65819] DEBUG -- : ALLO 1024
D, [2013-04-09T16:32:09.156882 #65819] DEBUG -- : 530 Not logged in
...D, [2013-04-09T16:32:09.164970 #65819] DEBUG -- : 220 wconrad/ftpd 0.6.0
D, [2013-04-09T16:32:09.165409 #65819] DEBUG -- : USER user
D, [2013-04-09T16:32:09.165521 #65819] DEBUG -- : 331 Password required
D, [2013-04-09T16:32:09.165687 #65819] DEBUG -- : PASS password
D, [2013-04-09T16:32:09.165774 #65819] DEBUG -- : 230 Logged in
D, [2013-04-09T16:32:09.166108 #65819] DEBUG -- : TYPE I
D, [2013-04-09T16:32:09.166244 #65819] DEBUG -- : 200 Type set to I
.D, [2013-04-09T16:32:09.171464 #65819] DEBUG -- : ALLO
D, [2013-04-09T16:32:09.171615 #65819] DEBUG -- : 501 Syntax error
...D, [2013-04-09T16:32:09.189221 #65819] DEBUG -- : 220 wconrad/ftpd 0.6.0
FSocket is not connected (Errno::ENOTCONN)
/private/tmp/ftpd/lib/ftpd/server.rb:112:in `shutdown'
/private/tmp/ftpd/lib/ftpd/server.rb:112:in `close_socket'
/private/tmp/ftpd/lib/ftpd/server.rb:101:in `block in start_session_thread'/private/tmp/ftpd/lib/ftpd/server.rb:112:in `shutdown': Socket is not connected (Errno::ENOTCONN)
    from /private/tmp/ftpd/lib/ftpd/server.rb:112:in `close_socket'
    from /private/tmp/ftpd/lib/ftpd/server.rb:101:in `block in start_session_thread'
rake aborted!
Command failed with status (1): [/Users/seph/.rbenv/versions/1.9.3-p327/bin...]

Tasks: TOP => test => test:cucumber => test:features
(See full trace by running task with --trace)

CPU usage pegged at 100%

Using bin/ftpdrb on macOS with ruby 1.4, Activity Monitor shows ruby using 6 threads and 100% of a CPU. Is the server doing a lot of polling? In my configuration, there are 3 active server connections that are otherwise idle most of the time.

Feature request: SIZE support

I'm using ftp.size from net::ftp & testing my client behavior w/ ftpd (which is awesome, thanks!). Would you be interested in a pull that supports SIZE, or would you want it to support all of rfc 3659?

Can I hooking filesystem operations

I want to create a ftp server only allow user upload files/directories.

After user upload finished, this server will automatically do some operations.

Can I do this thing use ftpd?

Simplify Telnet#telnet_state_machine

The Telnet class has an overly complex #state_machine_method. If it is possible to make the metric better without making the state machine any harder to follow, then do so.

State machines in code are difficult to understand, since they are a two-dimensional structure being represented in one dimension. The reason #state_machine_method is one large method is because it keeps the definition of the state machine on one page, which gives one a fighting chance to understand it. Any solution that spreads the actual state machine definition out, even though it might make the code metric better, would be inferior.

I thought this would be an interesting question for http://codereview.stackexchange.com .

I've got a branch where I've tried separating the state machine definition into a compact data structure. It looks pretty good, actually. I'm also going to try using the state-machine gem.

Simplify the Session "God" class

Ftpd::Session is a "God" class, with a Code Climate score of "F".

I am attempting to fix that by moving each command into its own class. Code shared between commands will move into helper classes and mixins. Session's state is moving into two dumb struct-like objects:

  • SessionConfig for all the static state that FtpServer gives to Session. This is already done.
  • SessionState for dynamic state that is created and maintained internally by Session

The idea is that a command handler will need only those two objects, plus the connected socket.

Doing all of this will make that metric better, but more importantly, it should make the commands able to be tested by rspec. The cucumber tests are valuable, but slow, and some error/edge cases are better tested by rspec.

Stopping an FtpServer instance raises an exception...

Currently, stopping a running FtpServer instance on OS X Mavericks in Ruby 1.9.3p374 raises and exception. For example:

require 'ftpd'

server = Ftpd::FtpServer.new(nil)
server.start
sleep 1
server.stop
sleep 1

raises an Errno::EBADF: Bad file descriptor exception.

This is due to the fact that the thread accepting connections has the socket closed out from under it by the thread calling Server#close. This raises an exception in the thread accepting connections which is not rescued and with abort_on_exception set to true, this exception blows through to the main thread. While this is fine if main thread has already exited or intends to exit, it cannot be handled if the main thread does not want to exit.

Streaming file transfers

When reading or writing a file from the file system, Ftpd loads the entire file in memory. This makes it a memory hog. What's worse, a client could kill Ftpd by transferring a very large file.

Rework the file system interface to support (but not necessarily require) streaming file transfers. The default file systems should provide streaming file transfers, so that anyone using them will benefit from the change.

The file system interface may be changed as needed to accomplish this. That is, compatability with previous versions of ftpd, for those who have created their own file system drivers, is not required.

UTF-8 On FileSystem API, Encoding::CompatibilityError

I'm writing a FTP server to perform file operations to a structure in the database.
The ftpd library is really a good job.

I currently meet a problem on string encoding. I'm writing the file system replacement as a database adapter, and get stuck on the exception ``block in dir': incompatible character encodings: ASCII-8BIT and UTF-8 (Encoding::CompatibilityError)`

My code:

def dir path
  path = path.gsub(/\/+\*?$/, '')
  nodelist = get_node(path).children.map do |node|
    path + '/' + node.name
  end
  nodelist
end

where get_node calls ActiveRecords and return a database record, which is in UTF-8.

It looks like the path parameter is provided as ASCII-8BIT. It won't fail until there are some CJK characters in the path (i.e. after CWD Japanese word ). It stores multi-bytes UTF-8 data inside ASCII-8BIT string.

    puts path
    puts path.encoding
    path = path.encode
    puts path
    puts path.encoding

/(2011Q1) べるぜバブ/*
ASCII-8BIT
/(2011Q1) ���������������/*
UTF-8

I'm using FlashFXP, with UTF-8 charset. (Or it will not display multi-bytes file names correctly)

I'm not quite sure how to deal with this problem.

External IP

I'm using Ftpd and loving every minute of it. It was so easy to subclass Ftpd::ReadOnlyDiskFileSystem and customize who can see what.

However, I'm running into an issue. Our server is behind a NAT and people are complaining about the "Server sent passive reply with unroutable address." error message they're seeing because Ftpd sends out our local address. Most clients fallback to using the server address, but I'm wondering if there's any way to properly fix the issue.

Is there a good way I can make Ftpd send clients our public IP? I know that there are some routers that will take care of this with packet rewriting, but we're using TLS so that option is out. The best I've gotten so far is this hack:

class TCPServer
  prepend(Module.new do
    def addr
      caller_locations.first.label == 'cmd_pasv' ? super.fill('203.0.113.197', 3, 1) : super
    end
  end)
end

This, of course, isn't ideal.

Modernize usage of File.dirname(__FILE__) to support symlinks

I tried to create a symlink to ftpd/bin/ftpdrb, but this broke the relative path name loading code:

unless $:.include?(File.dirname(__FILE__) + '/../lib')
  $:.unshift(File.dirname(__FILE__) + '/../lib')
end

This stack overflow article suggests using __dir__ as the equivalent of File.dirname(File.realpath(__FILE__)) which is precisely what's needed to compute the proper relative paths. Here's a patch:

diff --git a/bin/ftpdrb b/bin/ftpdrb
index 4513597..dcd222d 100755
--- a/bin/ftpdrb
+++ b/bin/ftpdrb
@@ -2,8 +2,8 @@
 #
 # Ftpd is a pure Ruby FTP server library. CLI version
 #
-unless $:.include?(File.dirname(__FILE__) + '/../lib')
-  $:.unshift(File.dirname(__FILE__) + '/../lib')
+unless $:.include?(__dir__ + '/../lib')
+  $:.unshift(__dir__ + '/../lib')
 end
 
 require 'ftpd'

add bindir in gemspec

The binary file bin/ftpdrb has not been linked in system correct path for executables. Generally it's /usr/local/bin/ftpdrb the contents of the current file is

#!/usr/bin/ruby2.4
#
# This file was generated by RubyGems.
#
# The application 'ftpd' is installed as part of a gem, and
# this file is here to facilitate running it.
#

require 'rubygems'

version = ">= 0.a"

if ARGV.first
  str = ARGV.first
  str = str.dup.force_encoding("BINARY") if str.respond_to? :force_encoding
  if str =~ /\A_(.*)_\z/ and Gem::Version.correct?($1) then
    version = $1
    ARGV.shift
  end
end

load Gem.activate_bin_path('ftpd', 'ftpdrb', version)

I belive the gemspec file is missing

s.bindir        = "bin"

FTP base path lack of trailing `/` results in denial of service and disclosure of the local file system path

The Ftpd::DiskFileSystem class suffers from a denial of service vulnerability and local file system path disclosure issues when the local file system path supplied in the constructor to be used as the base directory for the FTP server does not end with a trailing /.

This also includes temporary directories generated with Ftpd::TempDir.make which do not end with a trailing /. For example:

# irb
2.3.0 :001 > require 'ftpd'
 => true 
2.3.0 :002 > Ftpd::TempDir.make
 => "/tmp/d20170513-60691-1gn99k1"   # note: lack of trailing /

Tested on latest version of ftpd from GitHub repository (2.0.1).

The likelihood of this issue arising is increased due to the example code in the examples directory and the README which do not make use of a trailing /:

An excerpt from the README is shown below. Note the lack of the trailing /.

  def file_system(user)
    Ftpd::DiskFileSystem.new('/var/lib/ftp')
  end

Steps to Reproduce

Here's a simple example FTP script to reproduce these errors:

#!/usr/bin/env ruby

require 'ftpd'
require 'tmpdir'

class Driver
  def authenticate(user, password)
    true
  end
  def file_system(user)
    Ftpd::DiskFileSystem.new('/var/tmp/ftp')   # note: no trailing /
  end
end

driver = Driver.new
server = Ftpd::FtpServer.new(driver)
server.interface = '0.0.0.0'
server.port = 21
server.log = Logger.new($stdout)
server.start
puts "Server listening on port #{server.bound_port}"
gets

Denial of Service

It is possible to delete the base directory thus preventing users from uploading any additional files.

ftp> rmdir .
250 RMD command successful

Likewise, specifying rmdir .. will also delete the directory (note: it does NOT traverse and delete the parent directory, which is nice).

ftp> rmdir ..
250 RMD command successful

Here's a related excerpt from the server log:

# ./ftpd.rb 
Server listening on port 21
D, [2017-05-13T03:11:28.894137 #3962] DEBUG -- : 220 wconrad/ftpd 2.0.1
D, [2017-05-13T03:11:30.174613 #3962] DEBUG -- : USER asdf
D, [2017-05-13T03:11:30.174754 #3962] DEBUG -- : 331 Password required
D, [2017-05-13T03:11:30.782560 #3962] DEBUG -- : PASS **FILTERED**
D, [2017-05-13T03:11:30.782677 #3962] DEBUG -- : 230 Logged in
D, [2017-05-13T03:11:30.782796 #3962] DEBUG -- : SYST
D, [2017-05-13T03:11:30.782837 #3962] DEBUG -- : 215 UNIX Type: L8
D, [2017-05-13T03:11:32.488628 #3962] DEBUG -- : PORT 127,0,0,1,160,165
D, [2017-05-13T03:11:32.488804 #3962] DEBUG -- : 200 PORT command successful
D, [2017-05-13T03:11:32.488912 #3962] DEBUG -- : LIST
D, [2017-05-13T03:11:32.489077 #3962] DEBUG -- : 150 Opening ASCII mode data connection
D, [2017-05-13T03:11:32.489226 #3962] DEBUG -- : Sent 0 bytes
D, [2017-05-13T03:11:32.489354 #3962] DEBUG -- : 226 Transfer complete
D, [2017-05-13T03:11:34.668615 #3962] DEBUG -- : RMD .
D, [2017-05-13T03:11:34.668856 #3962] DEBUG -- : 250 RMD command successful

Local File System Path Disclosure

Various commands leak the local file system path of the ftpd base directory which may assist an attacker with further attacks against the system. This may also leak sensitive information in the event that the file system path contains sensitive information, such as client names, unique identifiers, PII, etc.

ftp> get . asdf
local: asdf remote: .
200 PORT command successful
150 Opening BINARY mode data connection
550 Is a directory @ io_fread - /var/tmp/ftp
ftp> get .. asdf
local: asdf remote: ..
200 PORT command successful
150 Opening BINARY mode data connection
550 Is a directory @ io_fread - /var/tmp/ftp
ftp> ren . asdf
350 RNFR accepted; ready for destination
550 Invalid argument @ rb_file_s_rename - (/var/tmp/ftp, /var/tmp/ftp/asdf)
ftp> ren .. asdf
350 RNFR accepted; ready for destination
550 Invalid argument @ rb_file_s_rename - (/var/tmp/ftp, /var/tmp/ftp/asdf)
ftp> del .
550 Is a directory @ unlink_internal - /var/tmp/ftp
ftp> del ..
550 Is a directory @ unlink_internal - /var/tmp/ftp

Although not directly related to the trailing slash issue, it's worth mentioning that attempting to delete a non-empty directory also discloses the local file system path.

ftp> ls asdf
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root          242 May 13 04:12 asdf
226 Transfer complete
ftp> rmdir asdf
550 Directory not empty @ dir_s_rmdir - /var/tmp/ftp/asdf

Notes

Note that these issues are also present when usnig Ftpd::TempDir.make

Here's some example output demonstrating some of these issues using the example code which makes use of Ftpd::TempDir.make.

Connected to 127.0.0.1.
220 wconrad/ftpd 2.0.1
Name (127.0.0.1:root): root
331 Password required
Password:
230 Logged in
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root           52 May 13 03:42 README
226 Transfer complete
ftp> del .
550 Is a directory @ unlink_internal - /tmp/d20170513-60051-1j26lxs
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root           52 May 13 03:42 README
226 Transfer complete
ftp> rmdir .
550 Directory not empty @ dir_s_rmdir - /tmp/d20170513-60051-1j26lxs
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
-rw-r--r-- 1 root     root           52 May 13 03:42 README
226 Transfer complete
ftp> del README
250 DELE command successful
ftp> rmdir .
250 RMD command successful
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
226 Transfer complete
ftp> 
ftp> put /etc/hosts asdf
local: /etc/hosts remote: asdf
200 PORT command successful
550 No such file or directory
ftp> ls
200 PORT command successful
150 Opening ASCII mode data connection
226 Transfer complete
ftp> 

Mitigation

To prevent disclosure of the file path, ensure 550 errors do not return the underlying error to the client.

To prevent denial of service by deleting the base directory, ensure the file system path specified in the Ftpd::DiskFileSystem constructor always makes use of a trailing slash /.

When the base path is specified with a trailing slash, a 550 Access denied error is returned in each instance identified above.

For example:

ftp> rmdir .
550 Access denied

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.