// Comments in relation to newssl5.patch by pitrou at // http://bugs.python.org/issue8108 which this patch is based on. // You are correct to call SSL_shutdown() twice in a row, since it is // possible OpenSSL has already read in from BIO but not yet // processed inside OpenSSL's incoming packet state machine the inbound // SSL end-of-stream marker. If the data is already in the BIO but // not yet processed then you can't wait for IO, since no more IO // may arrive. // My change to "if(err >= 0)" into "if(err > 0)" presumes you only // want this Python API call to return if SSL_shutdown() was // completely successful. Doing "if(err>=0) break;" is kind of odd // to see there (its more confusing to the 3rd party reader about // what the intention is). "if(err>=0)" breaks the behavior in the // cases when WANT_WRITE would be seen (at the first call to // SSL_shutdown()). Since the 2nd call may return "0" and then you // break out of your loop, which is different behavior to the more // usual "0" return followed by -1/WANT_READ sequence, which causes // you to stay in the loop until "1" is seen (since you will never // see "0" again). WANT_WRITE is a rare event to see. PySSL_BEGIN_ALLOW_THREADS /* 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 (unless the first call * doesn't return 0, which implies you saw SSL_ERROR_WANT_WRITE or * generic unrecoverable error) */ err = SSL_shutdown(self->ssl); PySSL_END_ALLOW_THREADS while (1) { char tmpbuf[64]; int n; 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. */ // FIXME raise exception, soft-error indicating this problem if(1) break; } if(err == 0) { PySSL_BEGIN_ALLOW_THREADS err = SSL_shutdown(self->ssl); PySSL_END_ALLOW_THREADS } /* if err==1 then a secure shutdown with SSL_shutdown() is complete */ if (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 */ }