summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/ssl.rst42
-rw-r--r--Lib/ssl.py6
-rw-r--r--Lib/test/test_ssl.py33
-rw-r--r--Misc/NEWS3
-rw-r--r--Modules/_ssl.c60
5 files changed, 120 insertions, 24 deletions
diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst
index 9f3760e..5232f1b 100644
--- a/Doc/library/ssl.rst
+++ b/Doc/library/ssl.rst
@@ -59,6 +59,48 @@ Functions, Constants, and Exceptions
.. versionchanged:: 3.3
:exc:`SSLError` used to be a subtype of :exc:`socket.error`.
+.. exception:: SSLZeroReturnError
+
+ A subclass of :exc:`SSLError` raised when trying to read or write and
+ the SSL connection has been closed cleanly. Note that this doesn't
+ mean that the underlying transport (read TCP) has been closed.
+
+ .. versionadded:: 3.3
+
+.. exception:: SSLWantReadError
+
+ A subclass of :exc:`SSLError` raised by a :ref:`non-blocking SSL socket
+ <ssl-nonblocking>` when trying to read or write data, but more data needs
+ to be received on the underlying TCP transport before the request can be
+ fulfilled.
+
+ .. versionadded:: 3.3
+
+.. exception:: SSLWantWriteError
+
+ A subclass of :exc:`SSLError` raised by a :ref:`non-blocking SSL socket
+ <ssl-nonblocking>` when trying to read or write data, but more data needs
+ to be sent on the underlying TCP transport before the request can be
+ fulfilled.
+
+ .. versionadded:: 3.3
+
+.. exception:: SSLSyscallError
+
+ A subclass of :exc:`SSLError` raised when a system error was encountered
+ while trying to fulfill an operation on a SSL socket. Unfortunately,
+ there is no easy way to inspect the original errno number.
+
+ .. versionadded:: 3.3
+
+.. exception:: SSLEOFError
+
+ A subclass of :exc:`SSLError` raised when the SSL connection has been
+ terminated abrupted. Generally, you shouldn't try to reuse the underlying
+ transport when this error is encountered.
+
+ .. versionadded:: 3.3
+
.. exception:: CertificateError
Raised to signal an error with a certificate (such as mismatching
diff --git a/Lib/ssl.py b/Lib/ssl.py
index 39cef2c..76f68f0 100644
--- a/Lib/ssl.py
+++ b/Lib/ssl.py
@@ -60,7 +60,11 @@ import re
import _ssl # if we can't import it, let the error propagate
from _ssl import OPENSSL_VERSION_NUMBER, OPENSSL_VERSION_INFO, OPENSSL_VERSION
-from _ssl import _SSLContext, SSLError
+from _ssl import _SSLContext
+from _ssl import (
+ SSLError, SSLZeroReturnError, SSLWantReadError, SSLWantWriteError,
+ SSLSyscallError, SSLEOFError,
+ )
from _ssl import CERT_NONE, CERT_OPTIONAL, CERT_REQUIRED
from _ssl import OP_ALL, OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_TLSv1
from _ssl import RAND_status, RAND_egd, RAND_add, RAND_bytes, RAND_pseudo_bytes
diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 25f3e4f..327bb84 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -619,13 +619,10 @@ class NetworkedTests(unittest.TestCase):
try:
s.do_handshake()
break
- except ssl.SSLError as err:
- if err.args[0] == ssl.SSL_ERROR_WANT_READ:
- select.select([s], [], [], 5.0)
- elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
- select.select([], [s], [], 5.0)
- else:
- raise
+ except ssl.SSLWantReadError:
+ select.select([s], [], [], 5.0)
+ except ssl.SSLWantWriteError:
+ select.select([], [s], [], 5.0)
# SSL established
self.assertTrue(s.getpeercert())
finally:
@@ -745,13 +742,10 @@ class NetworkedTests(unittest.TestCase):
count += 1
s.do_handshake()
break
- except ssl.SSLError as err:
- if err.args[0] == ssl.SSL_ERROR_WANT_READ:
- select.select([s], [], [])
- elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
- select.select([], [s], [])
- else:
- raise
+ except ssl.SSLWantReadError:
+ select.select([s], [], [])
+ except ssl.SSLWantWriteError:
+ select.select([], [s], [])
s.close()
if support.verbose:
sys.stdout.write("\nNeeded %d calls to do_handshake() to establish session.\n" % count)
@@ -1030,12 +1024,11 @@ else:
def _do_ssl_handshake(self):
try:
self.socket.do_handshake()
- except ssl.SSLError as 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()
+ except (ssl.SSLWantReadError, ssl.SSLWantWriteError):
+ return
+ except ssl.SSLEOFError:
+ return self.handle_close()
+ except ssl.SSLError:
raise
except socket.error as err:
if err.args[0] == errno.ECONNABORTED:
diff --git a/Misc/NEWS b/Misc/NEWS
index ca82490..4b6b828 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -341,6 +341,9 @@ Core and Builtins
Library
-------
+- Issue #11183: Add finer-grained exceptions to the ssl module, so that
+ you don't have to inspect the exception's attributes in the common case.
+
- Issue #13216: Add cp65001 codec, the Windows UTF-8 (CP_UTF8).
- Issue #13226: Add RTLD_xxx constants to the os module. These constants can be
diff --git a/Modules/_ssl.c b/Modules/_ssl.c
index 2998605..dcde4ff 100644
--- a/Modules/_ssl.c
+++ b/Modules/_ssl.c
@@ -99,6 +99,11 @@ static PySocketModule_APIObject PySocketModule;
/* SSL error object */
static PyObject *PySSLErrorObject;
+static PyObject *PySSLZeroReturnErrorObject;
+static PyObject *PySSLWantReadErrorObject;
+static PyObject *PySSLWantWriteErrorObject;
+static PyObject *PySSLSyscallErrorObject;
+static PyObject *PySSLEOFErrorObject;
#ifdef WITH_THREAD
@@ -191,6 +196,7 @@ static PyObject *
PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
{
PyObject *v;
+ PyObject *type = PySSLErrorObject;
char buf[2048];
char *errstr;
int err;
@@ -203,15 +209,18 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
switch (err) {
case SSL_ERROR_ZERO_RETURN:
- errstr = "TLS/SSL connection has been closed";
+ errstr = "TLS/SSL connection has been closed (EOF)";
+ type = PySSLZeroReturnErrorObject;
p = PY_SSL_ERROR_ZERO_RETURN;
break;
case SSL_ERROR_WANT_READ:
errstr = "The operation did not complete (read)";
+ type = PySSLWantReadErrorObject;
p = PY_SSL_ERROR_WANT_READ;
break;
case SSL_ERROR_WANT_WRITE:
p = PY_SSL_ERROR_WANT_WRITE;
+ type = PySSLWantWriteErrorObject;
errstr = "The operation did not complete (write)";
break;
case SSL_ERROR_WANT_X509_LOOKUP:
@@ -230,6 +239,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
= (PySocketSockObject *) PyWeakref_GetObject(obj->Socket);
if (ret == 0 || (((PyObject *)s) == Py_None)) {
p = PY_SSL_ERROR_EOF;
+ type = PySSLEOFErrorObject;
errstr = "EOF occurred in violation of protocol";
} else if (ret == -1) {
/* underlying BIO reported an I/O error */
@@ -240,6 +250,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
return v;
} else { /* possible? */
p = PY_SSL_ERROR_SYSCALL;
+ type = PySSLSyscallErrorObject;
errstr = "Some I/O error occurred";
}
} else {
@@ -272,7 +283,7 @@ PySSL_SetError(PySSLSocket *obj, int ret, char *filename, int lineno)
ERR_clear_error();
v = Py_BuildValue("(is)", p, buf);
if (v != NULL) {
- PyErr_SetObject(PySSLErrorObject, v);
+ PyErr_SetObject(type, v);
Py_DECREF(v);
}
return NULL;
@@ -2300,6 +2311,23 @@ parse_openssl_version(unsigned long libver,
PyDoc_STRVAR(SSLError_doc,
"An error occurred in the SSL implementation.");
+PyDoc_STRVAR(SSLZeroReturnError_doc,
+"SSL/TLS session closed cleanly.");
+
+PyDoc_STRVAR(SSLWantReadError_doc,
+"Non-blocking SSL socket needs to read more data\n"
+"before the requested operation can be completed.");
+
+PyDoc_STRVAR(SSLWantWriteError_doc,
+"Non-blocking SSL socket needs to write more data\n"
+"before the requested operation can be completed.");
+
+PyDoc_STRVAR(SSLSyscallError_doc,
+"System error when attempting SSL operation.");
+
+PyDoc_STRVAR(SSLEOFError_doc,
+"SSL/TLS connection terminated abruptly.");
+
PyMODINIT_FUNC
PyInit__ssl(void)
@@ -2343,7 +2371,33 @@ PyInit__ssl(void)
NULL);
if (PySSLErrorObject == NULL)
return NULL;
- if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0)
+ PySSLZeroReturnErrorObject = PyErr_NewExceptionWithDoc(
+ "ssl.SSLZeroReturnError", SSLZeroReturnError_doc,
+ PySSLErrorObject, NULL);
+ PySSLWantReadErrorObject = PyErr_NewExceptionWithDoc(
+ "ssl.SSLWantReadError", SSLWantReadError_doc,
+ PySSLErrorObject, NULL);
+ PySSLWantWriteErrorObject = PyErr_NewExceptionWithDoc(
+ "ssl.SSLWantWriteError", SSLWantWriteError_doc,
+ PySSLErrorObject, NULL);
+ PySSLSyscallErrorObject = PyErr_NewExceptionWithDoc(
+ "ssl.SSLSyscallError", SSLSyscallError_doc,
+ PySSLErrorObject, NULL);
+ PySSLEOFErrorObject = PyErr_NewExceptionWithDoc(
+ "ssl.SSLEOFError", SSLEOFError_doc,
+ PySSLErrorObject, NULL);
+ if (PySSLZeroReturnErrorObject == NULL
+ || PySSLWantReadErrorObject == NULL
+ || PySSLWantWriteErrorObject == NULL
+ || PySSLSyscallErrorObject == NULL
+ || PySSLEOFErrorObject == NULL)
+ return NULL;
+ if (PyDict_SetItemString(d, "SSLError", PySSLErrorObject) != 0
+ || PyDict_SetItemString(d, "SSLZeroReturnError", PySSLZeroReturnErrorObject) != 0
+ || PyDict_SetItemString(d, "SSLWantReadError", PySSLWantReadErrorObject) != 0
+ || PyDict_SetItemString(d, "SSLWantWriteError", PySSLWantWriteErrorObject) != 0
+ || PyDict_SetItemString(d, "SSLSyscallError", PySSLSyscallErrorObject) != 0
+ || PyDict_SetItemString(d, "SSLEOFError", PySSLEOFErrorObject) != 0)
return NULL;
if (PyDict_SetItemString(d, "_SSLContext",
(PyObject *)&PySSLContext_Type) != 0)