Issue 12139: add CCC command support to FTP_TLS class to revert the SSL connection back to clear-text.

This commit is contained in:
Giampaolo Rodola' 2011-06-27 11:17:51 +02:00
parent 504783975b
commit 096dcb1eff
4 changed files with 47 additions and 2 deletions

View File

@ -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. 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() .. method:: FTP_TLS.prot_p()
Set up secure data connection. Set up secure data connection.

View File

@ -192,6 +192,17 @@ The :mod:`ssl` module has new functions:
* :func:`~ssl.RAND_pseudo_bytes`: generate pseudo-random bytes. * :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 Optimizations
============= =============

View File

@ -708,6 +708,14 @@ else:
self.file = self.sock.makefile(mode='r', encoding=self.encoding) self.file = self.sock.makefile(mode='r', encoding=self.encoding)
return resp 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): def prot_p(self):
'''Set up secure data connection.''' '''Set up secure data connection.'''
# PROT defines whether or not the data channel is to be protected. # PROT defines whether or not the data channel is to be protected.

View File

@ -303,11 +303,11 @@ if ssl is not None:
_ssl_closing = False _ssl_closing = False
def secure_connection(self): def secure_connection(self):
self.del_channel()
socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False,
certfile=CERTFILE, server_side=True, certfile=CERTFILE, server_side=True,
do_handshake_on_connect=False, do_handshake_on_connect=False,
ssl_version=ssl.PROTOCOL_SSLv23) ssl_version=ssl.PROTOCOL_SSLv23)
self.del_channel()
self.set_socket(socket) self.set_socket(socket)
self._ssl_accepting = True self._ssl_accepting = True
@ -342,7 +342,10 @@ if ssl is not None:
# http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
pass pass
self._ssl_closing = False 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): def handle_read_event(self):
if self._ssl_accepting: if self._ssl_accepting:
@ -410,12 +413,18 @@ if ssl is not None:
def __init__(self, conn): def __init__(self, conn):
DummyFTPHandler.__init__(self, conn) DummyFTPHandler.__init__(self, conn)
self.secure_data_channel = False self.secure_data_channel = False
self._ccc = False
def cmd_auth(self, line): def cmd_auth(self, line):
"""Set up secure control channel.""" """Set up secure control channel."""
self.push('234 AUTH TLS successful') self.push('234 AUTH TLS successful')
self.secure_connection() 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): def cmd_pbsz(self, line):
"""Negotiate size of buffer for secure data transfer. """Negotiate size of buffer for secure data transfer.
For TLS/SSL the only valid value for the parameter is '0'. 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.assertIs(sock.context, ctx)
self.assertIsInstance(sock, ssl.SSLSocket) 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): class TestTimeouts(TestCase):