Commit fc69af15 authored by Antoine Pitrou's avatar Antoine Pitrou

Issue #5103: SSL handshake would ignore the socket timeout and block

indefinitely if the other end didn't respond.
parent 4d3e372f
...@@ -115,12 +115,7 @@ class SSLSocket(socket): ...@@ -115,12 +115,7 @@ class SSLSocket(socket):
cert_reqs, ssl_version, ca_certs, cert_reqs, ssl_version, ca_certs,
ciphers) ciphers)
if do_handshake_on_connect: if do_handshake_on_connect:
timeout = self.gettimeout() self.do_handshake()
try:
self.settimeout(None)
self.do_handshake()
finally:
self.settimeout(timeout)
self.keyfile = keyfile self.keyfile = keyfile
self.certfile = certfile self.certfile = certfile
self.cert_reqs = cert_reqs self.cert_reqs = cert_reqs
......
...@@ -10,6 +10,7 @@ import asynchat ...@@ -10,6 +10,7 @@ import asynchat
import socket import socket
import os import os
import time import time
import errno
from unittest import TestCase from unittest import TestCase
from test import test_support from test import test_support
...@@ -231,11 +232,37 @@ if hasattr(poplib, 'POP3_SSL'): ...@@ -231,11 +232,37 @@ if hasattr(poplib, 'POP3_SSL'):
def __init__(self, conn): def __init__(self, conn):
asynchat.async_chat.__init__(self, conn) asynchat.async_chat.__init__(self, conn)
self.socket = ssl.wrap_socket(self.socket, certfile=CERTFILE, self.socket = ssl.wrap_socket(self.socket, certfile=CERTFILE,
server_side=True) server_side=True,
do_handshake_on_connect=False)
# Must try handshake before calling push()
self._ssl_accepting = True
self._do_ssl_handshake()
self.set_terminator("\r\n") self.set_terminator("\r\n")
self.in_buffer = [] self.in_buffer = []
self.push('+OK dummy pop3 server ready.') self.push('+OK dummy pop3 server ready.')
def _do_ssl_handshake(self):
try:
self.socket.do_handshake()
except ssl.SSLError, err:
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE):
return
elif err.args[0] == ssl.SSL_ERROR_EOF:
return self.handle_close()
raise
except socket.error, err:
if err.args[0] == errno.ECONNABORTED:
return self.handle_close()
else:
self._ssl_accepting = False
def handle_read(self):
if self._ssl_accepting:
self._do_ssl_handshake()
else:
DummyPOP3Handler.handle_read(self)
class TestPOP3_SSLClass(TestPOP3Class): class TestPOP3_SSLClass(TestPOP3Class):
# repeat previous tests by using poplib.POP3_SSL # repeat previous tests by using poplib.POP3_SSL
......
...@@ -494,7 +494,8 @@ else: ...@@ -494,7 +494,8 @@ else:
asyncore.dispatcher_with_send.__init__(self, conn) asyncore.dispatcher_with_send.__init__(self, conn)
self.socket = ssl.wrap_socket(conn, server_side=True, self.socket = ssl.wrap_socket(conn, server_side=True,
certfile=certfile, certfile=certfile,
do_handshake_on_connect=True) do_handshake_on_connect=False)
self._ssl_accepting = True
def readable(self): def readable(self):
if isinstance(self.socket, ssl.SSLSocket): if isinstance(self.socket, ssl.SSLSocket):
...@@ -502,9 +503,28 @@ else: ...@@ -502,9 +503,28 @@ else:
self.handle_read_event() self.handle_read_event()
return True return True
def _do_ssl_handshake(self):
try:
self.socket.do_handshake()
except ssl.SSLError, err:
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE):
return
elif err.args[0] == ssl.SSL_ERROR_EOF:
return self.handle_close()
raise
except socket.error, err:
if err.args[0] == errno.ECONNABORTED:
return self.handle_close()
else:
self._ssl_accepting = False
def handle_read(self): def handle_read(self):
data = self.recv(1024) if self._ssl_accepting:
self.send(data.lower()) self._do_ssl_handshake()
else:
data = self.recv(1024)
self.send(data.lower())
def handle_close(self): def handle_close(self):
self.close() self.close()
...@@ -1271,6 +1291,53 @@ else: ...@@ -1271,6 +1291,53 @@ else:
server.stop() server.stop()
server.join() server.join()
def test_handshake_timeout(self):
# Issue #5103: SSL handshake must respect the socket timeout
server = socket.socket(socket.AF_INET)
host = "127.0.0.1"
port = test_support.bind_port(server)
started = threading.Event()
finish = False
def serve():
server.listen(5)
started.set()
conns = []
while not finish:
r, w, e = select.select([server], [], [], 0.1)
if server in r:
# Let the socket hang around rather than having
# it closed by garbage collection.
conns.append(server.accept()[0])
t = threading.Thread(target=serve)
t.start()
started.wait()
try:
try:
c = socket.socket(socket.AF_INET)
c.settimeout(0.2)
c.connect((host, port))
# Will attempt handshake and time out
self.assertRaisesRegexp(ssl.SSLError, "timed out",
ssl.wrap_socket, c)
finally:
c.close()
try:
c = socket.socket(socket.AF_INET)
c.settimeout(0.2)
c = ssl.wrap_socket(c)
# Will attempt handshake and time out
self.assertRaisesRegexp(ssl.SSLError, "timed out",
c.connect, (host, port))
finally:
c.close()
finally:
finish = True
t.join()
server.close()
def test_main(verbose=False): def test_main(verbose=False):
if skip_expected: if skip_expected:
......
...@@ -25,6 +25,9 @@ Core and Builtins ...@@ -25,6 +25,9 @@ Core and Builtins
Library Library
------- -------
- Issue #5103: SSL handshake would ignore the socket timeout and block
indefinitely if the other end didn't respond.
- The do_handshake() method of SSL objects now adjusts the blocking mode of - The do_handshake() method of SSL objects now adjusts the blocking mode of
the SSL structure if necessary (as other methods already do). the SSL structure if necessary (as other methods already do).
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment