Python FTP implicit TLS connection issue
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 ftplibimport sslclass 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()
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 ^_^
Implicit FTP over TLS with Passive transfer mode
Here's an implementation a little bit more "industrial".
Let's notice that in the previous examples the property named 'context' was missing in the init.
The code bellow works perfectly both with Python 2.7 and Python 3
The code
import ftplib, socket, sslFTPTLS_OBJ = ftplib.FTP_TLS# Class to manage implicit FTP over TLS connections, with passive transfer mode# - Important note:# If you connect to a VSFTPD server, check that the vsftpd.conf file contains# the property require_ssl_reuse=NOclass FTPTLS(FTPTLS_OBJ): host = "127.0.0.1" port = 990 user = "anonymous" timeout = 60 logLevel = 0 # Init both this and super def __init__(self, host=None, user=None, passwd=None, acct=None, keyfile=None, certfile=None, context=None, timeout=60): FTPTLS_OBJ.__init__(self, host, user, passwd, acct, keyfile, certfile, context, timeout) # Custom function: Open a new FTPS session (both connection & login) def openSession(self, host="127.0.0.1", port=990, user="anonymous", password=None, timeout=60): self.user = user # connect() ret = self.connect(host, port, timeout) # prot_p(): Set up secure data connection. try: ret = self.prot_p() if (self.logLevel > 1): self._log("INFO - FTPS prot_p() done: " + ret) except Exception as e: if (self.logLevel > 0): self._log("ERROR - FTPS prot_p() failed - " + str(e)) raise e # login() try: ret = self.login(user=user, passwd=password) if (self.logLevel > 1): self._log("INFO - FTPS login() done: " + ret) except Exception as e: if (self.logLevel > 0): self._log("ERROR - FTPS login() failed - " + str(e)) raise e if (self.logLevel > 1): self._log("INFO - FTPS session successfully opened") # Override function def connect(self, host="127.0.0.1", port=990, timeout=60): self.host = host self.port = port self.timeout = timeout try: self.sock = socket.create_connection((self.host, self.port), self.timeout) self.af = self.sock.family self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile) self.file = self.sock.makefile('r') self.welcome = self.getresp() if (self.logLevel > 1): self._log("INFO - FTPS connect() done: " + self.welcome) except Exception as e: if (self.logLevel > 0): self._log("ERROR - FTPS connect() failed - " + str(e)) raise e return self.welcome # Override function def makepasv(self): host, port = FTPTLS_OBJ.makepasv(self) # Change the host back to the original IP that was used for the connection host = socket.gethostbyname(self.host) return host, port # Custom function: Close the session def closeSession(self): try: self.close() if (self.logLevel > 1): self._log("INFO - FTPS close() done") except Exception as e: if (self.logLevel > 0): self._log("ERROR - FTPS close() failed - " + str(e)) raise e if (self.logLevel > 1): self._log("INFO - FTPS session successfully closed") # Private method for logs def _log(self, msg): # Be free here on how to implement your own way to redirect logs (e.g: to a console, to a file, etc.) print(msg)
Usage example
host = "www.myserver.com"port = 990user = "myUserId"password = "myPassword"myFtps = FTPTLS()myFtps.logLevel = 2myFtps.openSession(host, port, user, password)print(myFtps.retrlines("LIST"))myFtps.closeSession()
Output example
INFO - FTPS connect() done: 220 (vsFTPd 3.0.2)INFO - FTPS prot_p() done: 200 PROT now Private.INFO - FTPS login() done: 230 Login successful.INFO - FTPS session successfully opened-rw------- 1 ftp ftp 86735 Mar 22 16:55 MyModel.yaml-rw------- 1 ftp ftp 9298 Mar 22 16:55 MyData.csv226 Directory send OK.INFO - FTPS close() doneINFO - FTPS session successfully closed