diff options
author | Antoine Pitrou <solipsis@pitrou.net> | 2010-04-23 00:16:21 (GMT) |
---|---|---|
committer | Antoine Pitrou <solipsis@pitrou.net> | 2010-04-23 00:16:21 (GMT) |
commit | 2c4f98b3c58365845539f0d921c9d12b466b5e30 (patch) | |
tree | 2cd4c117bd36284ea749776aa1c2088817c28508 /Lib | |
parent | 582c0a6ac2cba5fd41222db04b94e7fc51389a08 (diff) | |
download | cpython-2c4f98b3c58365845539f0d921c9d12b466b5e30.zip cpython-2c4f98b3c58365845539f0d921c9d12b466b5e30.tar.gz cpython-2c4f98b3c58365845539f0d921c9d12b466b5e30.tar.bz2 |
Merged revisions 80392 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk
........
r80392 | antoine.pitrou | 2010-04-23 01:33:02 +0200 (ven., 23 avril 2010) | 9 lines
Issue #8108: Fix the unwrap() method of SSL objects when the socket has
a non-infinite timeout. Also make that method friendlier with applications
wanting to continue using the socket in clear-text mode, by disabling
OpenSSL's internal readahead. Thanks to Darryl Miles for guidance.
Issue #8108: test_ftplib's non-blocking SSL server now has proper handling
of SSL shutdowns.
........
Diffstat (limited to 'Lib')
-rw-r--r-- | Lib/test/test_ftplib.py | 49 |
1 files changed, 37 insertions, 12 deletions
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 29f7f7d..ab03faf 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -28,6 +28,7 @@ NLST_DATA = 'foo\r\nbar\r\n' class DummyDTPHandler(asynchat.async_chat): + dtp_conn_closed = False def __init__(self, conn, baseclass): asynchat.async_chat.__init__(self, conn) @@ -38,8 +39,13 @@ class DummyDTPHandler(asynchat.async_chat): self.baseclass.last_received_data += self.recv(1024).decode('ascii') def handle_close(self): - self.baseclass.push('226 transfer complete') - self.close() + # XXX: this method can be called many times in a row for a single + # connection, including in clear-text (non-TLS) mode. + # (behaviour witnessed with test_data_connection) + if not self.dtp_conn_closed: + self.baseclass.push('226 transfer complete') + self.close() + self.dtp_conn_closed = True def push(self, what): super(DummyDTPHandler, self).push(what.encode('ascii')) @@ -254,6 +260,7 @@ if ssl is not None: """An asyncore.dispatcher subclass supporting TLS/SSL.""" _ssl_accepting = False + _ssl_closing = False def secure_connection(self): self.del_channel() @@ -280,15 +287,36 @@ if ssl is not None: else: self._ssl_accepting = False + def _do_ssl_shutdown(self): + self._ssl_closing = True + try: + self.socket = self.socket.unwrap() + except ssl.SSLError as err: + if err.args[0] in (ssl.SSL_ERROR_WANT_READ, + ssl.SSL_ERROR_WANT_WRITE): + return + except socket.error as err: + # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return + # from OpenSSL's SSL_shutdown(), corresponding to a + # closed socket condition. See also: + # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html + pass + self._ssl_closing = False + super(SSLConnection, self).close() + def handle_read_event(self): if self._ssl_accepting: self._do_ssl_handshake() + elif self._ssl_closing: + self._do_ssl_shutdown() else: super(SSLConnection, self).handle_read_event() def handle_write_event(self): if self._ssl_accepting: self._do_ssl_handshake() + elif self._ssl_closing: + self._do_ssl_shutdown() else: super(SSLConnection, self).handle_write_event() @@ -308,7 +336,7 @@ if ssl is not None: except ssl.SSLError as err: if err.args[0] in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): - return '' + return b'' if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): self.handle_close() return b'' @@ -318,12 +346,9 @@ if ssl is not None: raise def close(self): - try: - if isinstance(self.socket, ssl.SSLSocket): - if self.socket._sslobj is not None: - self.socket.unwrap() - finally: - super(SSLConnection, self).close() + if (isinstance(self.socket, ssl.SSLSocket) and + self.socket._sslobj is not None): + self._do_ssl_shutdown() class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): @@ -606,21 +631,21 @@ class TestTLS_FTPClass(TestCase): sock = self.client.transfercmd('list') self.assertNotIsInstance(sock, ssl.SSLSocket) sock.close() - self.client.voidresp() + self.assertEqual(self.client.voidresp(), "226 transfer complete") # secured, after PROT P self.client.prot_p() sock = self.client.transfercmd('list') self.assertIsInstance(sock, ssl.SSLSocket) sock.close() - self.client.voidresp() + self.assertEqual(self.client.voidresp(), "226 transfer complete") # PROT C is issued, the connection must be in cleartext again self.client.prot_c() sock = self.client.transfercmd('list') self.assertNotIsInstance(sock, ssl.SSLSocket) sock.close() - self.client.voidresp() + self.assertEqual(self.client.voidresp(), "226 transfer complete") def test_login(self): # login() is supposed to implicitly secure the control connection |