From 096dcb1eff251071815b22f2c99512df25fa5ed6 Mon Sep 17 00:00:00 2001 From: Giampaolo Rodola' Date: Mon, 27 Jun 2011 11:17:51 +0200 Subject: [PATCH] Issue 12139: add CCC command support to FTP_TLS class to revert the SSL connection back to clear-text. --- Doc/library/ftplib.rst | 8 ++++++++ Doc/whatsnew/3.3.rst | 11 +++++++++++ Lib/ftplib.py | 8 ++++++++ Lib/test/test_ftplib.py | 22 ++++++++++++++++++++-- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/Doc/library/ftplib.rst b/Doc/library/ftplib.rst index f4205f4a9ef..087e200424c 100644 --- a/Doc/library/ftplib.rst +++ b/Doc/library/ftplib.rst @@ -426,6 +426,14 @@ FTP_TLS Objects Set up secure control connection by using TLS or SSL, depending on what specified in :meth:`ssl_version` attribute. +.. method:: FTP_TLS.ccc() + + Revert control channel back to plaintex. This can be useful to take + advantage of firewalls that know how to handle NAT with non-secure FTP + without opening fixed ports. + + .. versionadded:: 3.3 + .. method:: FTP_TLS.prot_p() Set up secure data connection. diff --git a/Doc/whatsnew/3.3.rst b/Doc/whatsnew/3.3.rst index 4374d0267d4..63f8b155c87 100644 --- a/Doc/whatsnew/3.3.rst +++ b/Doc/whatsnew/3.3.rst @@ -192,6 +192,17 @@ The :mod:`ssl` module has new functions: * :func:`~ssl.RAND_pseudo_bytes`: generate pseudo-random bytes. +ftplib +------ + +The :class:`~ftplib.FTP_TLS` class now provides a new +:func:`~ftplib.FTP_TLS.ccc` function to revert control channel back to +plaintex. This can be useful to take advantage of firewalls that know how to +handle NAT with non-secure FTP without opening fixed ports. + +(Patch submitted by Giampaolo RodolĂ  in :issue:`12139`.) + + Optimizations ============= diff --git a/Lib/ftplib.py b/Lib/ftplib.py index d15a1353d8b..eaaa6fd69c0 100644 --- a/Lib/ftplib.py +++ b/Lib/ftplib.py @@ -708,6 +708,14 @@ else: self.file = self.sock.makefile(mode='r', encoding=self.encoding) return resp + def ccc(self): + '''Switch back to a clear-text control connection.''' + if not isinstance(self.sock, ssl.SSLSocket): + raise ValueError("not using TLS") + resp = self.voidcmd('CCC') + self.sock = self.sock.unwrap() + return resp + def prot_p(self): '''Set up secure data connection.''' # PROT defines whether or not the data channel is to be protected. diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 78e0fb4977e..5734fd3453e 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -303,11 +303,11 @@ if ssl is not None: _ssl_closing = False def secure_connection(self): - self.del_channel() socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, certfile=CERTFILE, server_side=True, do_handshake_on_connect=False, ssl_version=ssl.PROTOCOL_SSLv23) + self.del_channel() self.set_socket(socket) self._ssl_accepting = True @@ -342,7 +342,10 @@ if ssl is not None: # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html pass self._ssl_closing = False - super(SSLConnection, self).close() + if getattr(self, '_ccc', False) == False: + super(SSLConnection, self).close() + else: + pass def handle_read_event(self): if self._ssl_accepting: @@ -410,12 +413,18 @@ if ssl is not None: def __init__(self, conn): DummyFTPHandler.__init__(self, conn) self.secure_data_channel = False + self._ccc = False def cmd_auth(self, line): """Set up secure control channel.""" self.push('234 AUTH TLS successful') self.secure_connection() + def cmd_ccc(self, line): + self.push('220 Reverting back to clear-text') + self._ccc = True + self._do_ssl_shutdown() + def cmd_pbsz(self, line): """Negotiate size of buffer for secure data transfer. For TLS/SSL the only valid value for the parameter is '0'. @@ -872,6 +881,15 @@ class TestTLS_FTPClass(TestCase): self.assertIs(sock.context, ctx) self.assertIsInstance(sock, ssl.SSLSocket) + def test_ccc(self): + self.assertRaises(ValueError, self.client.ccc) + self.client.login(secure=True) + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + self.client.ccc() + self.assertRaises(ValueError, self.client.sock.unwrap) + self.client.sendcmd('noop') + self.client.quit() + class TestTimeouts(TestCase):