[Python-checkins] r80392 - in python/trunk: Lib/test/test_ftplib.py Misc/NEWS Modules/_ssl.c
antoine.pitrou
python-checkins at python.org
Fri Apr 23 01:33:03 CEST 2010
Author: antoine.pitrou
Date: Fri Apr 23 01:33:02 2010
New Revision: 80392
Log:
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.
Modified:
python/trunk/Lib/test/test_ftplib.py
python/trunk/Misc/NEWS
python/trunk/Modules/_ssl.c
Modified: python/trunk/Lib/test/test_ftplib.py
==============================================================================
--- python/trunk/Lib/test/test_ftplib.py (original)
+++ python/trunk/Lib/test/test_ftplib.py Fri Apr 23 01:33:02 2010
@@ -29,6 +29,7 @@
class DummyDTPHandler(asynchat.async_chat):
+ dtp_conn_closed = False
def __init__(self, conn, baseclass):
asynchat.async_chat.__init__(self, conn)
@@ -39,8 +40,13 @@
self.baseclass.last_received_data += self.recv(1024)
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
class DummyFTPHandler(asynchat.async_chat):
@@ -253,6 +259,7 @@
"""An asyncore.dispatcher subclass supporting TLS/SSL."""
_ssl_accepting = False
+ _ssl_closing = False
def secure_connection(self):
self.socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False,
@@ -277,15 +284,36 @@
else:
self._ssl_accepting = False
+ def _do_ssl_shutdown(self):
+ self._ssl_closing = True
+ try:
+ self.socket = self.socket.unwrap()
+ except ssl.SSLError, err:
+ if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
+ ssl.SSL_ERROR_WANT_WRITE):
+ return
+ except socket.error, 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()
@@ -315,12 +343,9 @@
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):
@@ -597,21 +622,21 @@
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
Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS (original)
+++ python/trunk/Misc/NEWS Fri Apr 23 01:33:02 2010
@@ -25,6 +25,11 @@
Library
-------
+- 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 #8484: Load all ciphers and digest algorithms when initializing
the _ssl extension, such that verification of some SSL certificates
doesn't fail because of an "unknown algorithm".
@@ -68,6 +73,12 @@
- Build the ossaudio extension on GNU/kFreeBSD.
+Tests
+-----
+
+- Issue #8108: test_ftplib's non-blocking SSL server now has proper handling
+ of SSL shutdowns.
+
What's New in Python 2.7 beta 1?
================================
Modified: python/trunk/Modules/_ssl.c
==============================================================================
--- python/trunk/Modules/_ssl.c (original)
+++ python/trunk/Modules/_ssl.c Fri Apr 23 01:33:02 2010
@@ -9,6 +9,9 @@
directly.
XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE?
+
+ XXX integrate several "shutdown modes" as suggested in
+ http://bugs.python.org/issue8108#msg102867 ?
*/
#include "Python.h"
@@ -115,6 +118,7 @@
X509* peer_cert;
char server[X509_NAME_MAXLEN];
char issuer[X509_NAME_MAXLEN];
+ int shutdown_seen_zero;
} PySSLObject;
@@ -1357,7 +1361,8 @@
static PyObject *PySSL_SSLshutdown(PySSLObject *self)
{
- int err;
+ int err, ssl_err, sockstate, nonblocking;
+ int zeros = 0;
/* Guard against closed socket */
if (self->Socket->sock_fd < 0) {
@@ -1366,13 +1371,65 @@
return NULL;
}
- PySSL_BEGIN_ALLOW_THREADS
- err = SSL_shutdown(self->ssl);
- if (err == 0) {
- /* we need to call it again to finish the shutdown */
+ /* Just in case the blocking state of the socket has been changed */
+ nonblocking = (self->Socket->sock_timeout >= 0.0);
+ BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
+ BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
+
+ while (1) {
+ PySSL_BEGIN_ALLOW_THREADS
+ /* Disable read-ahead so that unwrap can work correctly.
+ * Otherwise OpenSSL might read in too much data,
+ * eating clear text data that happens to be
+ * transmitted after the SSL shutdown.
+ * Should be safe to call repeatedly everytime this
+ * function is used and the shutdown_seen_zero != 0
+ * condition is met.
+ */
+ if (self->shutdown_seen_zero)
+ SSL_set_read_ahead(self->ssl, 0);
err = SSL_shutdown(self->ssl);
+ PySSL_END_ALLOW_THREADS
+ /* If err == 1, a secure shutdown with SSL_shutdown() is complete */
+ if (err > 0)
+ break;
+ if (err == 0) {
+ /* Don't loop endlessly; instead preserve legacy
+ behaviour of trying SSL_shutdown() only twice.
+ This looks necessary for OpenSSL < 0.9.8m */
+ if (++zeros > 1)
+ break;
+ /* Shutdown was sent, now try receiving */
+ self->shutdown_seen_zero = 1;
+ continue;
+ }
+
+ /* Possibly retry shutdown until timeout or failure */
+ ssl_err = SSL_get_error(self->ssl, err);
+ if (ssl_err == SSL_ERROR_WANT_READ)
+ sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
+ else if (ssl_err == SSL_ERROR_WANT_WRITE)
+ sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
+ else
+ break;
+ if (sockstate == SOCKET_HAS_TIMED_OUT) {
+ if (ssl_err == SSL_ERROR_WANT_READ)
+ PyErr_SetString(PySSLErrorObject,
+ "The read operation timed out");
+ else
+ PyErr_SetString(PySSLErrorObject,
+ "The write operation timed out");
+ return NULL;
+ }
+ else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
+ PyErr_SetString(PySSLErrorObject,
+ "Underlying socket too large for select().");
+ return NULL;
+ }
+ else if (sockstate != SOCKET_OPERATION_OK)
+ /* Retain the SSL error code */
+ break;
}
- PySSL_END_ALLOW_THREADS
if (err < 0)
return PySSL_SetError(self, err, __FILE__, __LINE__);
More information about the Python-checkins
mailing list