Commit 9c3efe3e authored by Andrew M. Kuchling's avatar Andrew M. Kuchling

[Patch #945642] Fix non-blocking SSL sockets, which blocked on reads/writes in Python 2.3.

(It turns out that the Debian unstable packaging of Python 2.3.4 includes this patch.)
Patch by Tino Lange.
parent 91cc5cd1
...@@ -64,10 +64,19 @@ typedef struct { ...@@ -64,10 +64,19 @@ typedef struct {
static PyTypeObject PySSL_Type; static PyTypeObject PySSL_Type;
static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args); static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args);
static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args); static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args);
static int wait_for_timeout(PySocketSockObject *s, int writing); static int check_socket_and_wait_for_timeout(PySocketSockObject *s,
int writing);
#define PySSLObject_Check(v) ((v)->ob_type == &PySSL_Type) #define PySSLObject_Check(v) ((v)->ob_type == &PySSL_Type)
typedef enum {
SOCKET_IS_NONBLOCKING,
SOCKET_IS_BLOCKING,
SOCKET_HAS_TIMED_OUT,
SOCKET_HAS_BEEN_CLOSED,
SOCKET_OPERATION_OK
} timeout_state;
/* XXX It might be helpful to augment the error message generated /* XXX It might be helpful to augment the error message generated
below with the name of the SSL function that generated the error. below with the name of the SSL function that generated the error.
I expect it's obvious most of the time. I expect it's obvious most of the time.
...@@ -170,7 +179,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file) ...@@ -170,7 +179,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file)
char *errstr = NULL; char *errstr = NULL;
int ret; int ret;
int err; int err;
int timedout; int sockstate;
self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */ self = PyObject_New(PySSLObject, &PySSL_Type); /* Create new object */
if (self == NULL){ if (self == NULL){
...@@ -239,7 +248,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file) ...@@ -239,7 +248,7 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file)
/* Actually negotiate SSL connection */ /* Actually negotiate SSL connection */
/* XXX If SSL_connect() returns 0, it's also a failure. */ /* XXX If SSL_connect() returns 0, it's also a failure. */
timedout = 0; sockstate = 0;
do { do {
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
ret = SSL_connect(self->ssl); ret = SSL_connect(self->ssl);
...@@ -249,13 +258,20 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file) ...@@ -249,13 +258,20 @@ newPySSLObject(PySocketSockObject *Sock, char *key_file, char *cert_file)
goto fail; goto fail;
} }
if (err == SSL_ERROR_WANT_READ) { if (err == SSL_ERROR_WANT_READ) {
timedout = wait_for_timeout(Sock, 0); sockstate = check_socket_and_wait_for_timeout(Sock, 0);
} else if (err == SSL_ERROR_WANT_WRITE) { } else if (err == SSL_ERROR_WANT_WRITE) {
timedout = wait_for_timeout(Sock, 1); sockstate = check_socket_and_wait_for_timeout(Sock, 1);
} else {
sockstate = SOCKET_OPERATION_OK;
} }
if (timedout) { if (sockstate == SOCKET_HAS_TIMED_OUT) {
errstr = "The connect operation timed out"; PyErr_SetString(PySSLErrorObject, "The connect operation timed out");
goto fail;
} else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
PyErr_SetString(PySSLErrorObject, "Underlying socket has been closed.");
goto fail; goto fail;
} else if (sockstate == SOCKET_IS_NONBLOCKING) {
break;
} }
} while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE); } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
if (ret <= 0) { if (ret <= 0) {
...@@ -334,22 +350,25 @@ static void PySSL_dealloc(PySSLObject *self) ...@@ -334,22 +350,25 @@ static void PySSL_dealloc(PySSLObject *self)
/* If the socket has a timeout, do a select() on the socket. /* If the socket has a timeout, do a select() on the socket.
The argument writing indicates the direction. The argument writing indicates the direction.
Return non-zero if the socket timed out, zero otherwise. Returns one of the possibilities in the timeout_state enum (above).
*/ */
static int static int
wait_for_timeout(PySocketSockObject *s, int writing) check_socket_and_wait_for_timeout(PySocketSockObject *s, int writing)
{ {
fd_set fds; fd_set fds;
struct timeval tv; struct timeval tv;
int rc; int rc;
/* Nothing to do unless we're in timeout mode (not non-blocking) */ /* Nothing to do unless we're in timeout mode (not non-blocking) */
if (s->sock_timeout <= 0.0) if (s->sock_timeout < 0.0)
return 0; return SOCKET_IS_BLOCKING;
else if (s->sock_timeout == 0.0)
return SOCKET_IS_NONBLOCKING;
/* Guard against closed socket */ /* Guard against closed socket */
if (s->sock_fd < 0) if (s->sock_fd < 0)
return 0; return SOCKET_HAS_BEEN_CLOSED;
/* Construct the arguments to select */ /* Construct the arguments to select */
tv.tv_sec = (int)s->sock_timeout; tv.tv_sec = (int)s->sock_timeout;
...@@ -365,8 +384,9 @@ wait_for_timeout(PySocketSockObject *s, int writing) ...@@ -365,8 +384,9 @@ wait_for_timeout(PySocketSockObject *s, int writing)
rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv); rc = select(s->sock_fd+1, &fds, NULL, NULL, &tv);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
/* Return 1 on timeout, 0 otherwise */ /* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise
return rc == 0; (when we are able to write or when there's something to read) */
return rc == 0 ? SOCKET_HAS_TIMED_OUT : SOCKET_OPERATION_OK;
} }
static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args) static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
...@@ -374,16 +394,19 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args) ...@@ -374,16 +394,19 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
char *data; char *data;
int len; int len;
int count; int count;
int timedout; int sockstate;
int err; int err;
if (!PyArg_ParseTuple(args, "s#:write", &data, &count)) if (!PyArg_ParseTuple(args, "s#:write", &data, &count))
return NULL; return NULL;
timedout = wait_for_timeout(self->Socket, 1); sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
if (timedout) { if (sockstate == SOCKET_HAS_TIMED_OUT) {
PyErr_SetString(PySSLErrorObject, "The write operation timed out"); PyErr_SetString(PySSLErrorObject, "The write operation timed out");
return NULL; return NULL;
} else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
PyErr_SetString(PySSLErrorObject, "Underlying socket has been closed.");
return NULL;
} }
do { do {
err = 0; err = 0;
...@@ -395,13 +418,20 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args) ...@@ -395,13 +418,20 @@ static PyObject *PySSL_SSLwrite(PySSLObject *self, PyObject *args)
return NULL; return NULL;
} }
if (err == SSL_ERROR_WANT_READ) { if (err == SSL_ERROR_WANT_READ) {
timedout = wait_for_timeout(self->Socket, 0); sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
} else if (err == SSL_ERROR_WANT_WRITE) { } else if (err == SSL_ERROR_WANT_WRITE) {
timedout = wait_for_timeout(self->Socket, 1); sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
} else {
sockstate = SOCKET_OPERATION_OK;
} }
if (timedout) { if (sockstate == SOCKET_HAS_TIMED_OUT) {
PyErr_SetString(PySSLErrorObject, "The write operation timed out"); PyErr_SetString(PySSLErrorObject, "The write operation timed out");
return NULL; return NULL;
} else if (sockstate == SOCKET_HAS_BEEN_CLOSED) {
PyErr_SetString(PySSLErrorObject, "Underlying socket has been closed.");
return NULL;
} else if (sockstate == SOCKET_IS_NONBLOCKING) {
break;
} }
} while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE); } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
if (len > 0) if (len > 0)
...@@ -421,7 +451,7 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) ...@@ -421,7 +451,7 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
PyObject *buf; PyObject *buf;
int count = 0; int count = 0;
int len = 1024; int len = 1024;
int timedout; int sockstate;
int err; int err;
if (!PyArg_ParseTuple(args, "|i:read", &len)) if (!PyArg_ParseTuple(args, "|i:read", &len))
...@@ -430,8 +460,8 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) ...@@ -430,8 +460,8 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
if (!(buf = PyString_FromStringAndSize((char *) 0, len))) if (!(buf = PyString_FromStringAndSize((char *) 0, len)))
return NULL; return NULL;
timedout = wait_for_timeout(self->Socket, 0); sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
if (timedout) { if (sockstate == SOCKET_HAS_TIMED_OUT) {
PyErr_SetString(PySSLErrorObject, "The read operation timed out"); PyErr_SetString(PySSLErrorObject, "The read operation timed out");
Py_DECREF(buf); Py_DECREF(buf);
return NULL; return NULL;
...@@ -447,14 +477,18 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args) ...@@ -447,14 +477,18 @@ static PyObject *PySSL_SSLread(PySSLObject *self, PyObject *args)
return NULL; return NULL;
} }
if (err == SSL_ERROR_WANT_READ) { if (err == SSL_ERROR_WANT_READ) {
timedout = wait_for_timeout(self->Socket, 0); sockstate = check_socket_and_wait_for_timeout(self->Socket, 0);
} else if (err == SSL_ERROR_WANT_WRITE) { } else if (err == SSL_ERROR_WANT_WRITE) {
timedout = wait_for_timeout(self->Socket, 1); sockstate = check_socket_and_wait_for_timeout(self->Socket, 1);
} else {
sockstate = SOCKET_OPERATION_OK;
} }
if (timedout) { if (sockstate == SOCKET_HAS_TIMED_OUT) {
PyErr_SetString(PySSLErrorObject, "The read operation timed out"); PyErr_SetString(PySSLErrorObject, "The read operation timed out");
Py_DECREF(buf); Py_DECREF(buf);
return NULL; return NULL;
} else if (sockstate == SOCKET_IS_NONBLOCKING) {
break;
} }
} while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE); } while (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE);
if (count <= 0) { if (count <= 0) {
......
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