Index: Modules/_ssl.c =================================================================== --- Modules/_ssl.c (revision 79902) +++ Modules/_ssl.c (working copy) @@ -65,6 +65,11 @@ PY_SSL_VERSION_TLS1, }; +/* these constants are arbitary and specific to python */ +#define SSL_SHUTDOWN_MODE_ONCE (0) +#define SSL_SHUTDOWN_MODE_SENT (1) +#define SSL_SHUTDOWN_MODE_BOTH (3) + /* Include symbols from _socket module */ #include "socketmodule.h" @@ -115,6 +120,7 @@ X509* peer_cert; char server[X509_NAME_MAXLEN]; char issuer[X509_NAME_MAXLEN]; + int f_shutdown_seen_zero; } PySSLObject; @@ -661,7 +667,7 @@ X509_EXTENSION *ext = NULL; GENERAL_NAMES *names = NULL; GENERAL_NAME *name; - X509V3_EXT_METHOD *method; + const X509V3_EXT_METHOD *method; BIO *biobuf = NULL; char buf[2048]; char *vptr; @@ -1021,7 +1027,7 @@ static PyObject *PySSL_cipher (PySSLObject *self) { PyObject *retval, *v; - SSL_CIPHER *current; + const SSL_CIPHER *current; char *cipher_name; char *cipher_protocol; @@ -1343,10 +1349,23 @@ \n\ Read up to len bytes from the SSL socket."); -static PyObject *PySSL_SSLshutdown(PySSLObject *self) +static PyObject *PySSL_SSLshutdown(PySSLObject *self, PyObject *args) { + int sockstate; int err; + int ssl_err; + int nonblocking; + Py_ssize_t mode = SSL_SHUTDOWN_MODE_SENT; /* default when optional argument not given */ + if (!PyArg_ParseTuple(args, "|n:shutdown", &mode)) + return NULL; + + if (mode != SSL_SHUTDOWN_MODE_ONCE && mode != SSL_SHUTDOWN_MODE_SENT && mode != SSL_SHUTDOWN_MODE_BOTH) { + PyErr_SetString(PySSLErrorObject, + "Invalid 'mode' argument given."); + return NULL; + } + /* Guard against closed socket */ if (self->Socket->sock_fd < 0) { PyErr_SetString(PySSLErrorObject, @@ -1354,14 +1373,106 @@ return NULL; } + /* just in case the blocking state of the socket has been changed */ + nonblocking = (self->Socket->sock_timeout >= 0.0); + BIO_set_nbio(SSL_get_rbio(self->ssl), nonblocking); + BIO_set_nbio(SSL_get_wbio(self->ssl), nonblocking); + PySSL_BEGIN_ALLOW_THREADS + /* Disable read-ahead so that Python's unwrap can work correctly, + * should be safe to call repeatedly everytime this function is used + * and the f_shutdown_seen_zero!=0 condition is met. + */ + if (self->f_shutdown_seen_zero) + SSL_set_read_ahead(self->ssl, 0); + + /* The first call to SSL_shutdown() we ever make on the socket, + * attempts to write out our sides end-of-stream marker to indicate + * we shall never be sending any more data with SSL_write() over this + * SSL channel. + * The above is not true if we see SSL_ERROR_WANT_WRITE returned since + * that means the kernel buffer was too full to even accept that + * TLS/SSL packet containing our end-of-stream marker. + * It is correct to ensure you call SSL_shutdown() twice in a row before + * you start your sleep/wakup for IO if the first call returns 0. + */ err = SSL_shutdown(self->ssl); - if (err == 0) { - /* we need to call it again to finish the shutdown */ - err = SSL_shutdown(self->ssl); - } PySSL_END_ALLOW_THREADS + if (err == 0) + self->f_shutdown_seen_zero = 1; + while (mode != SSL_SHUTDOWN_MODE_ONCE) { + char tmpbuf[64]; + int n; + + if (mode == SSL_SHUTDOWN_MODE_BOTH && self->f_shutdown_seen_zero) { + PySSL_BEGIN_ALLOW_THREADS + n = SSL_peek(self->ssl, tmpbuf, sizeof(tmpbuf)); + PySSL_END_ALLOW_THREADS + + if (n > 0) { + /* Eek! There is still application data that the + * application needs to read, we can't correctly + * deal with a shutdown until that has been removed. + */ + PyErr_SetString(PySSLErrorObject, + "Trying to recv shutdown before we've sunk all data."); + return NULL; + } + } + + if (err == 0) { + PySSL_BEGIN_ALLOW_THREADS + /* Disable read-ahead so that Python's unwrap can work correctly, + * should be safe to call repeatedly everytime this function is used + * and the f_shutdown_seen_zero!=0 condition is met. + */ + if (self->f_shutdown_seen_zero) + SSL_set_read_ahead(self->ssl, 0); + + err = SSL_shutdown(self->ssl); + PySSL_END_ALLOW_THREADS + if (err == 0) + self->f_shutdown_seen_zero = 1; + } + + /* if err==1 then a secure shutdown with SSL_shutdown() is complete */ + if (mode == SSL_SHUTDOWN_MODE_SENT && err >= 0) + break; + else if (mode == SSL_SHUTDOWN_MODE_BOTH && err > 0) + break; + + /* Possibly retry shutdown until timeout or failure */ + if (err != 0) { + ssl_err = SSL_get_error(self->ssl, err); + if (ssl_err == SSL_ERROR_WANT_READ) + sockstate = check_socket_and_wait_for_timeout(self->Socket, 0); + else if (ssl_err == SSL_ERROR_WANT_WRITE) + sockstate = check_socket_and_wait_for_timeout(self->Socket, 1); + else + break; /* some other error that doesn't mean - OpenSSL handle good but waiting for new I/O event */ + 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; + } + + err = 0; /* ensures we always run SSL_shutdown() when we loop */ + } + if (err < 0) return PySSL_SetError(self, err, __FILE__, __LINE__); else { @@ -1371,10 +1482,15 @@ } PyDoc_STRVAR(PySSL_SSLshutdown_doc, -"shutdown(s) -> socket\n\ +"shutdown(s[, mode]) -> socket\n\ \n\ Does the SSL shutdown handshake with the remote end, and returns\n\ -the underlying socket object."); +the underlying socket object.\n\ +The optional mode argument indicates the condition we are waiting for\n\ +SSL_SHUTDOWN_MODE_SENT wait until we have sent our shutdown,\n\ +SSL_SHUTDOWN_MODE_BOTH wait until we have both (sent and received) shutdown,\n\ +SSL_SHUTDOWN_MODE_ONCE before using this mode read and understand OpenSSL\n\ +since the caller needs to handle the complex error returns itself (raw mode)."); static PyMethodDef PySSLMethods[] = { {"do_handshake", (PyCFunction)PySSL_SSLdo_handshake, METH_NOARGS}, @@ -1389,7 +1505,7 @@ {"peer_certificate", (PyCFunction)PySSL_peercert, METH_VARARGS, PySSL_peercert_doc}, {"cipher", (PyCFunction)PySSL_cipher, METH_NOARGS}, - {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_NOARGS, + {"shutdown", (PyCFunction)PySSL_SSLshutdown, METH_VARARGS, PySSL_SSLshutdown_doc}, {NULL, NULL} }; @@ -1647,6 +1763,14 @@ PyModule_AddIntConstant(m, "PROTOCOL_TLSv1", PY_SSL_VERSION_TLS1); + /* shutdown modes */ + PyModule_AddIntConstant(m, "SSL_SHUTDOWN_MODE_ONCE", + SSL_SHUTDOWN_MODE_ONCE); + PyModule_AddIntConstant(m, "SSL_SHUTDOWN_MODE_SENT", + SSL_SHUTDOWN_MODE_SENT); + PyModule_AddIntConstant(m, "SSL_SHUTDOWN_MODE_BOTH", + SSL_SHUTDOWN_MODE_BOTH); + /* OpenSSL version */ /* SSLeay() gives us the version of the library linked against, which could be different from the headers version.