Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2c4f98b

Browse files
committed
Merged revisions 80392 via svnmerge from
svn+ssh://[email protected]/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. ........
1 parent 582c0a6 commit 2c4f98b

3 files changed

Lines changed: 108 additions & 18 deletions

File tree

Lib/test/test_ftplib.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929

3030
class DummyDTPHandler(asynchat.async_chat):
31+
dtp_conn_closed = False
3132

3233
def __init__(self, conn, baseclass):
3334
asynchat.async_chat.__init__(self, conn)
@@ -38,8 +39,13 @@ def handle_read(self):
3839
self.baseclass.last_received_data += self.recv(1024).decode('ascii')
3940

4041
def handle_close(self):
41-
self.baseclass.push('226 transfer complete')
42-
self.close()
42+
# XXX: this method can be called many times in a row for a single
43+
# connection, including in clear-text (non-TLS) mode.
44+
# (behaviour witnessed with test_data_connection)
45+
if not self.dtp_conn_closed:
46+
self.baseclass.push('226 transfer complete')
47+
self.close()
48+
self.dtp_conn_closed = True
4349

4450
def push(self, what):
4551
super(DummyDTPHandler, self).push(what.encode('ascii'))
@@ -254,6 +260,7 @@ class SSLConnection(asyncore.dispatcher):
254260
"""An asyncore.dispatcher subclass supporting TLS/SSL."""
255261

256262
_ssl_accepting = False
263+
_ssl_closing = False
257264

258265
def secure_connection(self):
259266
self.del_channel()
@@ -280,15 +287,36 @@ def _do_ssl_handshake(self):
280287
else:
281288
self._ssl_accepting = False
282289

290+
def _do_ssl_shutdown(self):
291+
self._ssl_closing = True
292+
try:
293+
self.socket = self.socket.unwrap()
294+
except ssl.SSLError as err:
295+
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
296+
ssl.SSL_ERROR_WANT_WRITE):
297+
return
298+
except socket.error as err:
299+
# Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
300+
# from OpenSSL's SSL_shutdown(), corresponding to a
301+
# closed socket condition. See also:
302+
# http://www.mail-archive.com/[email protected]/msg60710.html
303+
pass
304+
self._ssl_closing = False
305+
super(SSLConnection, self).close()
306+
283307
def handle_read_event(self):
284308
if self._ssl_accepting:
285309
self._do_ssl_handshake()
310+
elif self._ssl_closing:
311+
self._do_ssl_shutdown()
286312
else:
287313
super(SSLConnection, self).handle_read_event()
288314

289315
def handle_write_event(self):
290316
if self._ssl_accepting:
291317
self._do_ssl_handshake()
318+
elif self._ssl_closing:
319+
self._do_ssl_shutdown()
292320
else:
293321
super(SSLConnection, self).handle_write_event()
294322

@@ -308,7 +336,7 @@ def recv(self, buffer_size):
308336
except ssl.SSLError as err:
309337
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
310338
ssl.SSL_ERROR_WANT_WRITE):
311-
return ''
339+
return b''
312340
if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):
313341
self.handle_close()
314342
return b''
@@ -318,12 +346,9 @@ def handle_error(self):
318346
raise
319347

320348
def close(self):
321-
try:
322-
if isinstance(self.socket, ssl.SSLSocket):
323-
if self.socket._sslobj is not None:
324-
self.socket.unwrap()
325-
finally:
326-
super(SSLConnection, self).close()
349+
if (isinstance(self.socket, ssl.SSLSocket) and
350+
self.socket._sslobj is not None):
351+
self._do_ssl_shutdown()
327352

328353

329354
class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler):
@@ -606,21 +631,21 @@ def test_data_connection(self):
606631
sock = self.client.transfercmd('list')
607632
self.assertNotIsInstance(sock, ssl.SSLSocket)
608633
sock.close()
609-
self.client.voidresp()
634+
self.assertEqual(self.client.voidresp(), "226 transfer complete")
610635

611636
# secured, after PROT P
612637
self.client.prot_p()
613638
sock = self.client.transfercmd('list')
614639
self.assertIsInstance(sock, ssl.SSLSocket)
615640
sock.close()
616-
self.client.voidresp()
641+
self.assertEqual(self.client.voidresp(), "226 transfer complete")
617642

618643
# PROT C is issued, the connection must be in cleartext again
619644
self.client.prot_c()
620645
sock = self.client.transfercmd('list')
621646
self.assertNotIsInstance(sock, ssl.SSLSocket)
622647
sock.close()
623-
self.client.voidresp()
648+
self.assertEqual(self.client.voidresp(), "226 transfer complete")
624649

625650
def test_login(self):
626651
# login() is supposed to implicitly secure the control connection

Misc/NEWS

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ C-API
329329
Library
330330
-------
331331

332+
- Issue #8108: Fix the unwrap() method of SSL objects when the socket has
333+
a non-infinite timeout. Also make that method friendlier with applications
334+
wanting to continue using the socket in clear-text mode, by disabling
335+
OpenSSL's internal readahead. Thanks to Darryl Miles for guidance.
336+
332337
- Issue #8496: make mailcap.lookup() always return a list, rather than an
333338
iterator. Patch by Gregory Nofi.
334339

@@ -1120,6 +1125,9 @@ Documentation
11201125
Tests
11211126
-----
11221127

1128+
- Issue #8108: test_ftplib's non-blocking SSL server now has proper handling
1129+
of SSL shutdowns.
1130+
11231131
- Issues #8279, #8330, #8437, #8480: Fix test_gdb failures, patch written by
11241132
Dave Malcolm
11251133

Modules/_ssl.c

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
directly.
1010
1111
XXX should partial writes be enabled, SSL_MODE_ENABLE_PARTIAL_WRITE?
12+
13+
XXX integrate several "shutdown modes" as suggested in
14+
http://bugs.python.org/issue8108#msg102867 ?
1215
*/
1316

1417
#include "Python.h"
@@ -116,6 +119,7 @@ typedef struct {
116119
SSL_CTX* ctx;
117120
SSL* ssl;
118121
X509* peer_cert;
122+
int shutdown_seen_zero;
119123

120124
} PySSLObject;
121125

@@ -1392,7 +1396,8 @@ Read up to len bytes from the SSL socket.");
13921396

13931397
static PyObject *PySSL_SSLshutdown(PySSLObject *self)
13941398
{
1395-
int err;
1399+
int err, ssl_err, sockstate, nonblocking;
1400+
int zeros = 0;
13961401
PySocketSockObject *sock
13971402
= (PySocketSockObject *) PyWeakref_GetObject(self->Socket);
13981403

@@ -1403,13 +1408,65 @@ static PyObject *PySSL_SSLshutdown(PySSLObject *self)
14031408
return NULL;
14041409
}
14051410

1406-
PySSL_BEGIN_ALLOW_THREADS
1407-
err = SSL_shutdown(self->ssl);
1408-
if (err == 0) {
1409-
/* we need to call it again to finish the shutdown */
1411+
/* Just in case the blocking state of the socket has been changed */
1412+
nonblocking = (sock->sock_timeout >= 0.0);
1413+
BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking);
1414+
BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking);
1415+
1416+
while (1) {
1417+
PySSL_BEGIN_ALLOW_THREADS
1418+
/* Disable read-ahead so that unwrap can work correctly.
1419+
* Otherwise OpenSSL might read in too much data,
1420+
* eating clear text data that happens to be
1421+
* transmitted after the SSL shutdown.
1422+
* Should be safe to call repeatedly everytime this
1423+
* function is used and the shutdown_seen_zero != 0
1424+
* condition is met.
1425+
*/
1426+
if (self->shutdown_seen_zero)
1427+
SSL_set_read_ahead(self->ssl, 0);
14101428
err = SSL_shutdown(self->ssl);
1429+
PySSL_END_ALLOW_THREADS
1430+
/* If err == 1, a secure shutdown with SSL_shutdown() is complete */
1431+
if (err > 0)
1432+
break;
1433+
if (err == 0) {
1434+
/* Don't loop endlessly; instead preserve legacy
1435+
behaviour of trying SSL_shutdown() only twice.
1436+
This looks necessary for OpenSSL < 0.9.8m */
1437+
if (++zeros > 1)
1438+
break;
1439+
/* Shutdown was sent, now try receiving */
1440+
self->shutdown_seen_zero = 1;
1441+
continue;
1442+
}
1443+
1444+
/* Possibly retry shutdown until timeout or failure */
1445+
ssl_err = SSL_get_error(self->ssl, err);
1446+
if (ssl_err == SSL_ERROR_WANT_READ)
1447+
sockstate = check_socket_and_wait_for_timeout(sock, 0);
1448+
else if (ssl_err == SSL_ERROR_WANT_WRITE)
1449+
sockstate = check_socket_and_wait_for_timeout(sock, 1);
1450+
else
1451+
break;
1452+
if (sockstate == SOCKET_HAS_TIMED_OUT) {
1453+
if (ssl_err == SSL_ERROR_WANT_READ)
1454+
PyErr_SetString(PySSLErrorObject,
1455+
"The read operation timed out");
1456+
else
1457+
PyErr_SetString(PySSLErrorObject,
1458+
"The write operation timed out");
1459+
return NULL;
1460+
}
1461+
else if (sockstate == SOCKET_TOO_LARGE_FOR_SELECT) {
1462+
PyErr_SetString(PySSLErrorObject,
1463+
"Underlying socket too large for select().");
1464+
return NULL;
1465+
}
1466+
else if (sockstate != SOCKET_OPERATION_OK)
1467+
/* Retain the SSL error code */
1468+
break;
14111469
}
1412-
PySSL_END_ALLOW_THREADS
14131470

14141471
if (err < 0)
14151472
return PySSL_SetError(self, err, __FILE__, __LINE__);

0 commit comments

Comments
 (0)