giampaolo / pyftpdlib

Extremely fast and scalable Python FTP server library
MIT License
1.65k stars 266 forks source link

Add Implicit SSL support. #160

Open giampaolo opened 10 years ago

giampaolo commented 10 years ago

From btimby@gmail.com on March 19, 2011 02:18:11

Currently pyftpdlib provides SSL/TLS support in the form of Explicit SSL. 
http://en.wikipedia.org/wiki/FTPS#Explicit This mode requires the client to 
secure the channel after connecting. It is generally more flexible and useful 
that Implicit SSL. However, many users desire Implicit SSL. 
http://en.wikipedia.org/wiki/FTPS#Implicit Supporting this feature makes it 
easier for the user to configure their FTP client (choose any SSL option and it 
just works).

That said, I wonder how hard it would be to implement this feature. I see a 
couple of hurdles.

1. The asyncore.dispatcher only allows a bind() to a single address/port.
2. If a second FTPServer (FTPImplicitSSLServer) instance is used, how does it 
participate in the asyncore.loop()?

The rest is simply modifying the Handlers (FTPHandler, DTPHandler) to 
immediately secure the channel.

Original issue: http://code.google.com/p/pyftpdlib/issues/detail?id=160

giampaolo commented 10 years ago

From btimby@gmail.com on March 21, 2011 12:38:07

This is actually trivial to implement. I have only two small issues.

Example:
--
class SSLImplicitFTPHandler(FTPHandler):
    def handle(self):
        self.secure_connection(self.ssl_context)

    def handle_ssl_established(self):
        FTPHandler.handle(self)

    def ftp_AUTH(self, arg):
        self.respond("550 not supposed to be used with implicit SSL.")

    ftpes = ftpserver.FTPServer(('0.0.0.0', 21))
    ftpis = ftpserver.FTPServer(('0.0.0.0', 990))
    ftpes.serve_forever()
-- Issue 1 : Upon timeout, Filezilla reports the following.

15:17:37    Status: Directory listing successful
15:22:37    Response:   421 Control connection timed out.
15:22:37    Error:  GnuTLS error -9: A TLS packet with unexpected length was received.
15:22:37    Status: Server did not properly shut down TLS connection
15:22:37    Error:  Could not read from socket: ECONNABORTED - Connection aborted
15:22:37    Error:  Disconnected from server

You can see this occurs 5 minutes after the last successful command. Something 
is not being shut down properly server-side in response to a timeout. Issue 2 : 
This is trivial, however, calling serve_forever() on one of the FTPServer 
instances seems a bit hacky, perhaps this function could be a module-level 
function (ftpserver.serve_forever()). The FTPServer class could then call this 
and issue a deprecation warning. I think this might make more sense in the case 
of running multiple FTP servers in the same process, there are actually a 
couple scenarios for doing this: multiple ports, multiple IPs, implicit SSL 
(actually a variation of multiple ports).
giampaolo commented 10 years ago

From btimby@gmail.com on March 21, 2011 12:41:05

Sorry, my example had indentation issues due to me copying/pasting from 
different locations in a .py script.

Example:
--
class SSLImplicitFTPHandler(ftpserver.FTPHandler):
    def handle(self):
        self.secure_connection(self.ssl_context)

    def handle_ssl_established(self):
        FTPHandler.handle(self)

    def ftp_AUTH(self, arg):
        self.respond("550 not supposed to be used with implicit SSL.")

ftpes = ftpserver.FTPHandler(('0.0.0.0', 21))
ftpis = SSLImplicitFTPHandler(('0.0.0.0', 990))
ftpes.serve_forever()
--
giampaolo commented 10 years ago

From g.rodola on March 22, 2011 05:36:57

> Upon timeout, Filezilla reports the following.

It seems we should invoke SSL shutdown() also for the control connection.
I opened issue 162 for this and fixed it in r840 .

+1 on adding a new ImplicitTLS_FTPHandler class.
+0, for now, about a module level serve_forever() method because it occurred to 
me there are implications with max_cons and max_cons_per_ip options and also 
with close_all().
giampaolo commented 10 years ago

From btimby@gmail.com on March 22, 2011 07:37:19

Brilliant! That change resolves the SSL timeout issue.

The other issue is so low priority it is not worth worrying about.

Thanks.
giampaolo commented 10 years ago

From g.rodola on March 22, 2011 07:42:23

;)
giampaolo commented 10 years ago

From btimby@gmail.com on March 22, 2011 07:48:14

Here is a simple patch to provide an ImplicitTLS_Handler class.

Attachment: contrib-handlers-implicit_tls_handler.patch

giampaolo commented 10 years ago

From btimby@gmail.com on March 22, 2011 08:37:44

A simple patch adding ImplicitTLS_FTPHandler support to demo/tls_ftpd.py.

Attachment: demo-tls_ftpd-implicit_tls_handler.patch

giampaolo commented 10 years ago

From btimby@gmail.com on March 23, 2011 13:10:11

Here is a patch that includes the above two patches (with some fixes) and a 
modified unit test suite.

3 tests are failing, but this is likely due to the FTP_TLS client and not the 
server itself.

I will fix these tests when I have some time.

Attachment: implicit-ssl.patch

giampaolo commented 10 years ago

From btimby@gmail.com on March 28, 2011 11:14:31

I am going to need some help with these failing tests. I have been working on 
it for a few hours. I tried to extrude the FTPd class and the FTPISClient class 
into separate test files and they work together. They just don't apparently 
work under the test suite. It always hangs on the client at the 
ssl.wrapsocket() call.
mrinterestfull commented 9 years ago

Hello, Any update on this issue? Can we raise the priority? So far we have used this as a workaround: http://stackoverflow.com/questions/12164470/python-ftp-tls-connection-issue but that is giving us problems after upgrade to Debian Jessie and python 2.7.9:

 File "/usr/lib/python2.7/ftplib.py", line 752, in storbinary
    conn.unwrap()
  File "/usr/lib/python2.7/ssl.py", line 771, in unwrap
    s = self._sslobj.shutdown()
socket.error: [Errno 0] Error

python3.4.2

  File "/usr/lib/python3.4/ftplib.py", line 740, in login
    return FTP.login(self, user, passwd, acct)
  File "/usr/lib/python3.4/ftplib.py", line 417, in login
    resp = self.sendcmd('USER ' + user)
  File "/usr/lib/python3.4/ftplib.py", line 272, in sendcmd
    return self.getresp()
  File "/usr/lib/python3.4/ftplib.py", line 235, in getresp
    resp = self.getmultiline()
  File "/usr/lib/python3.4/ftplib.py", line 221, in getmultiline
    line = self.getline()
  File "/usr/lib/python3.4/ftplib.py", line 212, in getline
    elif line[-1:] in CRLF:
davidkhess commented 3 years ago

I also have a need for this feature. Any progress or recipe where we can make it work?

davidkhess commented 3 years ago

If this is still available on a branch somewhere, I'm willing to work on it as a PR.

airtightdesign commented 1 month ago

Started work on this based on example code in this comment. The work in progress is in this fork branch 160-add-implicit-ssl-support

Was able to get a semi-functional implicit FTPS based off example usage in tutorial FTPS (FTP over TLS/SSL) server replacing TLS_FTPHandler with SSLImplicitFTPHandler

curl 'ftps://REDACTED:REDACTED@example.com' Gets the error curl: (60) SSL certificate problem: unable to get local issuer certificate, but successfully lists files with --insecure

Correction: Appears we have functional implicit FTPS based off example usage in tutorial FTPS (FTP over TLS/SSL) server replacing TLS_FTPHandler with SSLImplicitFTPHandler.

The problem was configuration. In case someone else has the same problem, ftp_handler.certfile should be the fullchain1.pem file, not cert1.pem.