Python FTP implicit TLS connection issue

Solution 1:

Extending the solutions that have been proposed so far, the issue is that implicit FTPS connections need the socket to be ssl wrapped automatically, before we get a chance to call login(). A lot of the subclasses that people are proposing do this in the context of the connect method, we can more generally manage this by modifying the get/set of self.sock with a property to auto-wrap on set:

import ftplib
import ssl

class ImplicitFTP_TLS(ftplib.FTP_TLS):
    """FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sock = None

    @property
    def sock(self):
        """Return the socket."""
        return self._sock

    @sock.setter
    def sock(self, value):
        """When modifying the socket, ensure that it is ssl wrapped."""
        if value is not None and not isinstance(value, ssl.SSLSocket):
            value = self.context.wrap_socket(value)
        self._sock = value

Usage is essentially the same as with the standard FTP_TLS class:

ftp_client = ImplicitFTP_TLS()
ftp_client.connect(host='ftp.example.com', port=990)
ftp_client.login(user='USERNAME', passwd='PASSWORD')
ftp_client.prot_p()

Solution 2:

I've worked on the same problem for half a day and finally figured it out.

For the implicit FTP TLS/SSL(defualt port 990), our client program must build a TLS/SSL connection right after the socket is created. But python's class FTP_TLS doesn't reload the connect function from class FTP. We need to fix it:

class tyFTP(ftplib.FTP_TLS):
  def __init__(self,
               host='',
               user='',
               passwd='',
               acct='',
               keyfile=None,
               certfile=None,
               timeout=60):

    ftplib.FTP_TLS.__init__(self,
                            host=host,
                            user=user,
                            passwd=passwd,
                            acct=acct,
                            keyfile=keyfile,
                            certfile=certfile,
                            timeout=timeout)

  def connect(self, host='', port=0, timeout=-999):
    """Connect to host.  Arguments are:
    - host: hostname to connect to (string, default previous host)
    - port: port to connect to (integer, default previous port)
    """
    if host != '':
        self.host = host
    if port > 0:
        self.port = port
    if timeout != -999:
        self.timeout = timeout
    try:
        self.sock = socket.create_connection((self.host, self.port), self.timeout)
        self.af = self.sock.family
        # add this line!!!
        self.sock = ssl.wrap_socket(self.sock,
                                    self.keyfile,
                                    self.certfile,
                                    ssl_version=ssl.PROTOCOL_TLSv1)
        # add end
        self.file = self.sock.makefile('rb')
        self.welcome = self.getresp()
    except Exception as e:
        print(e)
    return self.welcome

This derived class reloads the connect function and builds a wrapper around the socket to TLS. After you successfully connect and login to FTP server, you need to call: FTP_TLS.prot_p() before executing any FTP command!

Hope this will help ^_^