From 5a1c4d18804b44cffdf8c5fdacad7b01a7505036 Mon Sep 17 00:00:00 2001 From: Antoine Pitrou Date: Fri, 23 Apr 2010 21:11:10 +0000 Subject: [PATCH] Merged revisions 80394 via svnmerge from svn+ssh://pythondev@svn.python.org/python/branches/py3k ................ r80394 | antoine.pitrou | 2010-04-23 02:16:21 +0200 (ven., 23 avril 2010) | 15 lines 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. ........ ................ --- Misc/NEWS | 5 ++++ Modules/_ssl.c | 69 +++++++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 68 insertions(+), 6 deletions(-) diff --git a/Misc/NEWS b/Misc/NEWS index 980cd69d1cd..a619192ff0d 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -40,6 +40,11 @@ Core and Builtins 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 #8468: bz2.BZ2File() accepts str with surrogates and bytes filenames - Issue #8496: make mailcap.lookup() always return a list, rather than an diff --git a/Modules/_ssl.c b/Modules/_ssl.c index 5311f771e73..5e0f473d405 100644 --- a/Modules/_ssl.c +++ b/Modules/_ssl.c @@ -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" @@ -116,6 +119,7 @@ typedef struct { SSL_CTX* ctx; SSL* ssl; X509* peer_cert; + int shutdown_seen_zero; } PySSLObject; @@ -1377,7 +1381,8 @@ Read up to len bytes from the SSL socket."); static PyObject *PySSL_SSLshutdown(PySSLObject *self) { - int err; + int err, ssl_err, sockstate, nonblocking; + int zeros = 0; PySocketSockObject *sock = (PySocketSockObject *) PyWeakref_GetObject(self->Socket); @@ -1388,13 +1393,65 @@ static PyObject *PySSL_SSLshutdown(PySSLObject *self) 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 = (sock->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(sock, 0); + else if (ssl_err == SSL_ERROR_WANT_WRITE) + sockstate = check_socket_and_wait_for_timeout(sock, 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__);