summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Bowes <jbowes@redhat.com>2006-10-23 20:43:39 +0200
committerMatěj Cepl <mcepl@cepl.eu>2015-10-13 12:17:07 +0200
commit1b5f8bbe04280558dfe577a2df203eaf0600c473 (patch)
tree280f69477f036623cdf0f44af154e3dbad396153
parent49d300db3c331567be7bd86e6d8f92236486f43b (diff)
downloadm2crypto-1b5f8bbe04280558dfe577a2df203eaf0600c473.tar.gz
Add support for SSL socket timeouts
See for discussion of this patch https://bugzilla.redhat.com/show_bug.cgi?id=210966 This patch adds timeout to SSL Operations, but it breaks API. From the linked bug comment: This patch seems to change the API, likely breaking any currently working code that uses nonblocking sockets. - the patch completely changes the semantics of non-blocking operations (with timeout==0.0): instead of returning 0L, None or -1, they would raise exceptions - although SSL.Connection._{read,write}_{,n}bio uses the "internal use" naming convention, they are used in the shipped demo and contrib code, so I wouldn't be surprised if they were used in applications as well - SSL.Connection.makefile() supports write-only and read-write file objects, while SSLFile is supports only reading. ALSO, BE AWARE THAT THIS BUG USES LINUX SYSTEM CALL POLL() IN ITS C PART, SO IT IS NOT WORKING ON WINDOWS.
-rw-r--r--M2Crypto/SSL/Connection.py59
-rw-r--r--M2Crypto/SSL/__init__.py5
-rw-r--r--SWIG/_ssl.i260
-rw-r--r--tests/test_ssl.py93
4 files changed, 307 insertions, 110 deletions
diff --git a/M2Crypto/SSL/Connection.py b/M2Crypto/SSL/Connection.py
index ef6a2c8..5d2fb96 100644
--- a/M2Crypto/SSL/Connection.py
+++ b/M2Crypto/SSL/Connection.py
@@ -41,15 +41,17 @@ class Connection:
def __init__(self, ctx, sock=None):
self.ctx = ctx
self.ssl = m2.ssl_new(self.ctx.ctx)
- if sock is not None:
+ if sock is not None:
self.socket = sock
else:
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._fileno = self.socket.fileno()
-
- self.blocking = self.socket.gettimeout()
-
+
+ self._timeout = self.socket.gettimeout()
+ if self._timeout is None:
+ self._timeout = -1.0
+
self.ssl_close_flag = m2.bio_noclose
@@ -83,7 +85,7 @@ class Connection:
self.socket.bind(addr)
def listen(self, qlen=5):
- self.socket.listen(qlen)
+ self.socket.listen(qlen)
def ssl_get_error(self, ret):
return m2.ssl_get_error(self.ssl, ret)
@@ -93,15 +95,15 @@ class Connection:
Explicitly set read and write bios
"""
m2.ssl_set_bio(self.ssl, readbio._ptr(), writebio._ptr())
-
+
def set_client_CA_list_from_file(self, cafile):
"""
Set the acceptable client CA list. If the client
returns a certificate, it must have been issued by
one of the CAs listed in cafile.
-
+
Makes sense only for servers.
-
+
@param cafile: Filename from which to load the CA list.
"""
m2.ssl_set_client_CA_list_from_file(self.ssl, cafile)
@@ -111,7 +113,7 @@ class Connection:
Set the acceptable client CA list. If the client
returns a certificate, it must have been issued by
one of the CAs listed in context.
-
+
Makes sense only for servers.
"""
m2.ssl_set_client_CA_list_from_context(self.ssl, self.ctx.ctx)
@@ -147,7 +149,7 @@ class Connection:
m2.ssl_set_accept_state(self.ssl)
def accept_ssl(self):
- return m2.ssl_accept(self.ssl)
+ return m2.ssl_accept(self.ssl, self._timeout)
def accept(self):
"""Accept an SSL connection. The return value is a pair (ssl, addr) where
@@ -169,7 +171,7 @@ class Connection:
m2.ssl_set_connect_state(self.ssl)
def connect_ssl(self):
- return m2.ssl_connect(self.ssl)
+ return m2.ssl_connect(self.ssl, self._timeout)
def connect(self, addr):
self.socket.connect(addr)
@@ -191,12 +193,12 @@ class Connection:
return m2.ssl_renegotiate(self.ssl)
def pending(self):
- """Return the numbers of octets that can be read from the
+ """Return the numbers of octets that can be read from the
connection."""
return m2.ssl_pending(self.ssl)
def _write_bio(self, data):
- return m2.ssl_write(self.ssl, data)
+ return m2.ssl_write(self.ssl, data, self._timeout)
def _write_nbio(self, data):
return m2.ssl_write_nbio(self.ssl, data)
@@ -204,7 +206,7 @@ class Connection:
def _read_bio(self, size=1024):
if size <= 0:
raise ValueError, 'size <= 0'
- return m2.ssl_read(self.ssl, size)
+ return m2.ssl_read(self.ssl, size, self._timeout)
def _read_nbio(self, size=1024):
if size <= 0:
@@ -212,13 +214,13 @@ class Connection:
return m2.ssl_read_nbio(self.ssl, size)
def write(self, data):
- if self.blocking:
+ if self._timeout != 0.0:
return self._write_bio(data)
return self._write_nbio(data)
sendall = send = write
def read(self, size=1024):
- if self.blocking:
+ if self._timeout != 0.0:
return self._read_bio(size)
return self._read_nbio(size)
recv = read
@@ -226,7 +228,17 @@ class Connection:
def setblocking(self, mode):
"""Set this connection's underlying socket to _mode_."""
self.socket.setblocking(mode)
- self.blocking = mode
+ if mode:
+ self._timeout = -1.0
+ else:
+ self._timeout = 0.0
+
+ def settimeout(self, timeout):
+ """Set this connection's underlying socket's timeout to _timeout_."""
+ self.socket.settimeout(timeout)
+ self._timeout = timeout
+ if self._timeout is None:
+ self._timeout = -1.0
def fileno(self):
return self.socket.fileno()
@@ -238,7 +250,7 @@ class Connection:
return apply(self.socket.setsockopt, args)
def get_context(self):
- """Return the SSL.Context object associated with this
+ """Return the SSL.Context object associated with this
connection."""
return m2.ssl_get_ssl_ctx(self.ssl)
@@ -308,15 +320,8 @@ class Connection:
"""Set the cipher suites for this connection."""
return m2.ssl_set_cipher_list(self.ssl, cipher_list)
- def makefile(self, mode='rb', bufsize='ignored'):
- r = 'r' in mode or '+' in mode
- w = 'w' in mode or 'a' in mode or '+' in mode
- b = 'b' in mode
- m2mode = ['', 'r'][r] + ['', 'w'][w] + ['', 'b'][b]
- # XXX Need to dup().
- bio = BIO.BIO(self.sslbio, _close_cb=self.close)
- m2.bio_do_handshake(bio._ptr())
- return BIO.IOBuffer(bio, m2mode, _pyfree=0)
+ def makefile(self, mode='rb', bufsize=-1):
+ return socket._fileobject(self, mode, bufsize)
def getsockname(self):
return self.socket.getsockname()
diff --git a/M2Crypto/SSL/__init__.py b/M2Crypto/SSL/__init__.py
index a013569..00d4e54 100644
--- a/M2Crypto/SSL/__init__.py
+++ b/M2Crypto/SSL/__init__.py
@@ -2,11 +2,14 @@
Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+import socket
+
# M2Crypto
from M2Crypto import m2
class SSLError(Exception): pass
-m2.ssl_init(SSLError)
+class SSLTimeoutError(SSLError, socket.timeout): pass
+m2.ssl_init(SSLError, SSLTimeoutError)
# M2Crypto.SSL
from Cipher import Cipher, Cipher_Stack
diff --git a/SWIG/_ssl.i b/SWIG/_ssl.i
index 28a247c..ccfece9 100644
--- a/SWIG/_ssl.i
+++ b/SWIG/_ssl.i
@@ -11,10 +11,13 @@
%{
#include <pythread.h>
+#include <limits.h>
#include <openssl/bio.h>
#include <openssl/dh.h>
#include <openssl/ssl.h>
#include <openssl/x509.h>
+#include <poll.h>
+#include <sys/time.h>
%}
%apply Pointer NONNULL { SSL_CTX * };
@@ -155,6 +158,11 @@ extern long SSL_SESSION_set_timeout(SSL_SESSION *, long);
%rename(ssl_session_get_timeout) SSL_SESSION_get_timeout;
extern long SSL_SESSION_get_timeout(CONST SSL_SESSION *);
+extern PyObject *ssl_accept(SSL *ssl, double timeout = -1);
+extern PyObject *ssl_connect(SSL *ssl, double timeout = -1);
+extern PyObject *ssl_read(SSL *ssl, int num, double timeout = -1);
+extern int ssl_write(SSL *ssl, PyObject *blob, double timeout = -1);
+
%constant int ssl_error_none = SSL_ERROR_NONE;
%constant int ssl_error_ssl = SSL_ERROR_SSL;
%constant int ssl_error_want_read = SSL_ERROR_WANT_READ;
@@ -210,14 +218,19 @@ extern long SSL_SESSION_get_timeout(CONST SSL_SESSION *);
%constant int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = SSL_MODE_ENABLE_PARTIAL_WRITE;
%constant int SSL_MODE_AUTO_RETRY = SSL_MODE_AUTO_RETRY;
+%ignore ssl_handle_error;
+%ignore ssl_sleep_with_timeout;
%inline %{
static PyObject *_ssl_err;
+static PyObject *_ssl_timeout_err;
-void ssl_init(PyObject *ssl_err) {
+void ssl_init(PyObject *ssl_err, PyObject *ssl_timeout_err) {
SSL_library_init();
SSL_load_error_strings();
Py_INCREF(ssl_err);
+ Py_INCREF(ssl_timeout_err);
_ssl_err = ssl_err;
+ _ssl_timeout_err = ssl_timeout_err;
}
void ssl_ctx_passphrase_callback(SSL_CTX *ctx, PyObject *pyfunc) {
@@ -403,36 +416,130 @@ int ssl_set_fd(SSL *ssl, int fd) {
return ret;
}
-PyObject *ssl_accept(SSL *ssl) {
+static void ssl_handle_error(int ssl_err, int ret) {
+ int err;
+
+ switch (ssl_err) {
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err,
+ ERR_reason_error_string(ERR_get_error()));
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+ else if (ret == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (ret == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ else
+ assert(0);
+ break;
+ default:
+ PyErr_SetString(_ssl_err, "unexpected SSL error");
+ }
+}
+
+static int ssl_sleep_with_timeout(SSL *ssl, const struct timeval *start,
+ double timeout, int ssl_err) {
+ struct pollfd fd;
+ struct timeval tv;
+ int ms, tmp;
+
+ assert(timeout > 0);
+ again:
+ gettimeofday(&tv, NULL);
+ /* tv >= start */
+ if ((timeout + start->tv_sec - tv.tv_sec) > INT_MAX / 1000)
+ ms = -1;
+ else {
+ int fract;
+
+ ms = ((start->tv_sec + (int)timeout) - tv.tv_sec) * 1000;
+ fract = (start->tv_usec + (timeout - (int)timeout) * 1000000
+ - tv.tv_usec + 999) / 1000;
+ if (ms > 0 && fract > INT_MAX - ms)
+ ms = -1;
+ else {
+ ms += fract;
+ if (ms <= 0)
+ goto timeout;
+ }
+ }
+ switch (ssl_err) {
+ case SSL_ERROR_WANT_READ:
+ fd.fd = SSL_get_rfd(ssl);
+ fd.events = POLLIN;
+ break;
+
+ case SSL_ERROR_WANT_WRITE:
+ fd.fd = SSL_get_wfd(ssl);
+ fd.events = POLLOUT;
+ break;
+
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ return 0; /* FIXME: is this correct? */
+
+ default:
+ assert(0);
+ }
+ if (fd.fd == -1) {
+ PyErr_SetString(_ssl_err, "timeout on a non-FD SSL");
+ return -1;
+ }
+ Py_BEGIN_ALLOW_THREADS
+ tmp = poll(&fd, 1, ms);
+ Py_END_ALLOW_THREADS
+ switch (tmp) {
+ case 1:
+ return 0;
+ case 0:
+ goto timeout;
+ case -1:
+ if (errno == EINTR)
+ goto again;
+ PyErr_SetFromErrno(_ssl_err);
+ return -1;
+ }
+ return 0;
+
+ timeout:
+ PyErr_SetString(_ssl_timeout_err, "timed out");
+ return -1;
+}
+
+PyObject *ssl_accept(SSL *ssl, double timeout) {
PyObject *obj = NULL;
- int r, err;
+ int r, ssl_err;
+ struct timeval tv;
+ if (timeout > 0)
+ gettimeofday(&tv, NULL);
+ again:
Py_BEGIN_ALLOW_THREADS
r = SSL_accept(ssl);
+ ssl_err = SSL_get_error(ssl, r);
Py_END_ALLOW_THREADS
- switch (SSL_get_error(ssl, r)) {
+ switch (ssl_err) {
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
obj = PyInt_FromLong((long)1);
break;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
- obj = PyInt_FromLong((long)0);
- break;
- case SSL_ERROR_SSL:
- PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ if (timeout <= 0) {
+ obj = PyInt_FromLong((long)0);
+ break;
+ }
+ if (ssl_sleep_with_timeout(ssl, &tv, timeout, ssl_err) == 0)
+ goto again;
obj = NULL;
break;
+ case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
- err = ERR_get_error();
- if (err)
- PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
- else if (r == 0)
- PyErr_SetString(_ssl_err, "unexpected eof");
- else if (r == -1)
- PyErr_SetFromErrno(_ssl_err);
+ ssl_handle_error(ssl_err, r);
obj = NULL;
break;
}
@@ -441,36 +548,38 @@ PyObject *ssl_accept(SSL *ssl) {
return obj;
}
-PyObject *ssl_connect(SSL *ssl) {
+PyObject *ssl_connect(SSL *ssl, double timeout) {
PyObject *obj = NULL;
- int r, err;
+ int r, ssl_err;
+ struct timeval tv;
+ if (timeout > 0)
+ gettimeofday(&tv, NULL);
+ again:
Py_BEGIN_ALLOW_THREADS
r = SSL_connect(ssl);
+ ssl_err = SSL_get_error(ssl, r);
Py_END_ALLOW_THREADS
- switch (SSL_get_error(ssl, r)) {
+ switch (ssl_err) {
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
obj = PyInt_FromLong((long)1);
break;
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
- obj = PyInt_FromLong((long)0);
- break;
- case SSL_ERROR_SSL:
- PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ if (timeout <= 0) {
+ obj = PyInt_FromLong((long)0);
+ break;
+ }
+ if (ssl_sleep_with_timeout(ssl, &tv, timeout, ssl_err) == 0)
+ goto again;
obj = NULL;
break;
+ case SSL_ERROR_SSL:
case SSL_ERROR_SYSCALL:
- err = ERR_get_error();
- if (err)
- PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
- else if (r == 0)
- PyErr_SetString(_ssl_err, "unexpected eof");
- else if (r == -1)
- PyErr_SetFromErrno(_ssl_err);
+ ssl_handle_error(ssl_err, r);
obj = NULL;
break;
}
@@ -483,10 +592,11 @@ void ssl_set_shutdown1(SSL *ssl, int mode) {
SSL_set_shutdown(ssl, mode);
}
-PyObject *ssl_read(SSL *ssl, int num) {
+PyObject *ssl_read(SSL *ssl, int num, double timeout) {
PyObject *obj = NULL;
void *buf;
- int r, err;
+ int r;
+ struct timeval tv;
if (!(buf = PyMem_Malloc(num))) {
PyErr_SetString(PyExc_MemoryError, "ssl_read");
@@ -494,37 +604,44 @@ PyObject *ssl_read(SSL *ssl, int num) {
}
+ if (timeout > 0)
+ gettimeofday(&tv, NULL);
+ again:
Py_BEGIN_ALLOW_THREADS
r = SSL_read(ssl, buf, num);
Py_END_ALLOW_THREADS
- switch (SSL_get_error(ssl, r)) {
- case SSL_ERROR_NONE:
- case SSL_ERROR_ZERO_RETURN:
- buf = PyMem_Realloc(buf, r);
- obj = PyString_FromStringAndSize(buf, r);
- break;
- case SSL_ERROR_WANT_WRITE:
- case SSL_ERROR_WANT_READ:
- case SSL_ERROR_WANT_X509_LOOKUP:
- Py_INCREF(Py_None);
- obj = Py_None;
- break;
- case SSL_ERROR_SSL:
- PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
- obj = NULL;
- break;
- case SSL_ERROR_SYSCALL:
- err = ERR_get_error();
- if (err)
- PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
- else if (r == 0)
- PyErr_SetString(_ssl_err, "unexpected eof");
- else if (r == -1)
- PyErr_SetFromErrno(_ssl_err);
- obj = NULL;
- break;
+ if (r >= 0) {
+ buf = PyMem_Realloc(buf, r);
+ obj = PyString_FromStringAndSize(buf, r);
+ } else {
+ int ssl_err;
+
+ ssl_err = SSL_get_error(ssl, r);
+ switch (ssl_err) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ assert(0);
+
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ if (timeout <= 0) {
+ Py_INCREF(Py_None);
+ obj = Py_None;
+ break;
+ }
+ if (ssl_sleep_with_timeout(ssl, &tv, timeout, ssl_err) == 0)
+ goto again;
+ obj = NULL;
+ break;
+ case SSL_ERROR_SSL:
+ case SSL_ERROR_SYSCALL:
+ ssl_handle_error(ssl_err, r);
+ obj = NULL;
+ break;
+ }
}
PyMem_Free(buf);
@@ -582,22 +699,26 @@ PyObject *ssl_read_nbio(SSL *ssl, int num) {
return obj;
}
-int ssl_write(SSL *ssl, PyObject *blob) {
+int ssl_write(SSL *ssl, PyObject *blob, double timeout) {
const void *buf;
- int len, r, err, ret;
+ int len, r, ssl_err, ret;
+ struct timeval tv;
if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) {
return -1;
}
-
+ if (timeout > 0)
+ gettimeofday(&tv, NULL);
+ again:
Py_BEGIN_ALLOW_THREADS
r = SSL_write(ssl, buf, len);
+ ssl_err = SSL_get_error(ssl, r);
Py_END_ALLOW_THREADS
- switch (SSL_get_error(ssl, r)) {
+ switch (ssl_err) {
case SSL_ERROR_NONE:
case SSL_ERROR_ZERO_RETURN:
ret = r;
@@ -605,20 +726,17 @@ int ssl_write(SSL *ssl, PyObject *blob) {
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_X509_LOOKUP:
+ if (timeout <= 0) {
+ ret = -1;
+ break;
+ }
+ if (ssl_sleep_with_timeout(ssl, &tv, timeout, ssl_err) == 0)
+ goto again;
ret = -1;
break;
case SSL_ERROR_SSL:
- PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
- ret = -1;
- break;
case SSL_ERROR_SYSCALL:
- err = ERR_get_error();
- if (err)
- PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
- else if (r == 0)
- PyErr_SetString(_ssl_err, "unexpected eof");
- else if (r == -1)
- PyErr_SetFromErrno(_ssl_err);
+ ssl_handle_error(ssl_err, r);
default:
ret = -1;
}
diff --git a/tests/test_ssl.py b/tests/test_ssl.py
index 701484d..fc857a8 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -36,7 +36,7 @@ def verify_cb_new_function(ok, store):
m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
m2.X509_V_ERR_CERT_UNTRUSTED,
m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE]
- assert store.get_error_depth() == 0
+ assert store.get_error_depth() == 0
app_data = m2.x509_store_ctx_get_app_data(store.ctx)
assert app_data
x509 = store.get_current_cert()
@@ -47,7 +47,7 @@ def verify_cb_new_function(ok, store):
except AssertionError, e:
# If we let exceptions propagate from here the
# caller may see strange errors. This is cleaner.
- return 0
+ return 0
return 1
class VerifyCB:
@@ -99,7 +99,7 @@ class BaseSSLClientTestCase(unittest.TestCase):
os.waitpid(pid, 0)
def http_get(self, s):
- s.send('GET / HTTP/1.0\n\n')
+ s.send('GET / HTTP/1.0\n\n')
resp = ''
while 1:
try:
@@ -108,7 +108,7 @@ class BaseSSLClientTestCase(unittest.TestCase):
break
except SSL.SSLError: # s_server throws an 'unexpected eof'...
break
- resp = resp + r
+ resp = resp + r
return resp
def setUp(self):
@@ -716,7 +716,7 @@ class MiscSSLClientTestCase(BaseSSLClientTestCase):
self.stop_server(pid)
def test_verify_cert_mutual_auth(self):
- self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])
+ self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])
pid = self.start_server(self.args)
try:
ctx = SSL.Context()
@@ -754,7 +754,7 @@ class MiscSSLClientTestCase(BaseSSLClientTestCase):
self.failIf(string.find(data, 's_server -quiet -www') == -1)
def test_verify_cert_mutual_auth_fail(self):
- self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])
+ self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])
pid = self.start_server(self.args)
try:
ctx = SSL.Context()
@@ -911,7 +911,7 @@ class Urllib2SSLClientTestCase(BaseSSLClientTestCase):
from M2Crypto import m2urllib2
opener = m2urllib2.build_opener(ctx)
- opener.addheaders = [('Connection', 'close')]
+ opener.addheaders = [('Connection', 'close')]
u = opener.open('https://%s:%s/' % (srv_host, srv_port))
data = u.read()
u.close()
@@ -972,6 +972,77 @@ class Urllib2SSLClientTestCase(BaseSSLClientTestCase):
class TwistedSSLClientTestCase(BaseSSLClientTestCase):
+ def test_timeout(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ # Just a really small number so we can timeout
+ s.settimeout(0.000000000000000000000000000001)
+ self.assertRaises(SSL.SSLTimeoutError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_makefile_timeout(self):
+ # httpslib uses makefile to read the response
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ c = httpslib.HTTPS(srv_host, srv_port)
+ c.putrequest('GET', '/')
+ c.putheader('Accept', 'text/html')
+ c.putheader('Accept', 'text/plain')
+ c.endheaders()
+ c._conn.sock.settimeout(100)
+ err, msg, headers = c.getreply()
+ assert err == 200, err
+ f = c.getfile()
+ data = f.read()
+ c.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_makefile_timeout_fires(self):
+ # This is convoluted because (openssl s_server -www) starts writing the
+ # response as soon as it receives the first line of the request, so it's
+ # possible for it to send the response before the request is sent and
+ # there would be no timeout. So, let the server spend time reading from
+ # an empty pipe
+ FIFO_NAME = 'test_makefile_timeout_fires_fifo'
+ os.mkfifo('tests/' + FIFO_NAME)
+ pipe_pid = os.fork()
+ try:
+ if pipe_pid == 0:
+ try:
+ f = open('tests/' + FIFO_NAME, 'w')
+ try:
+ time.sleep(sleepTime + 1)
+ f.write('Content\n')
+ finally:
+ f.close()
+ finally:
+ os._exit(0)
+ self.args[self.args.index('-www')] = '-WWW'
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ c = httpslib.HTTPS(srv_host, srv_port)
+ c.putrequest('GET', '/' + FIFO_NAME)
+ c.putheader('Accept', 'text/html')
+ c.putheader('Accept', 'text/plain')
+ c.endheaders()
+ c._conn.sock.settimeout(0.0000000001)
+ self.assertRaises(socket.timeout, c.getreply)
+ c.close()
+ finally:
+ self.stop_server(pid)
+ finally:
+ os.kill(pipe_pid, 1)
+ os.waitpid(pipe_pid, 0)
+ os.unlink('tests/' + FIFO_NAME)
+
def test_twisted_wrapper(self):
# Test only when twisted and ZopeInterfaces are present
try:
@@ -1067,12 +1138,12 @@ def suite():
suite.addTest(unittest.makeSuite(TwistedSSLClientTestCase))
except ImportError:
pass
- return suite
-
+ return suite
+
def zap_servers():
s = 's_server'
- fn = tempfile.mktemp()
+ fn = tempfile.mktemp()
cmd = 'ps | egrep %s > %s' % (s, fn)
os.system(cmd)
f = open(fn)
@@ -1097,7 +1168,7 @@ if __name__ == '__main__':
gc.set_debug(gc.DEBUG_LEAK & ~gc.DEBUG_SAVEALL)
try:
- Rand.load_file('randpool.dat', -1)
+ Rand.load_file('randpool.dat', -1)
unittest.TextTestRunner().run(suite())
Rand.save_file('randpool.dat')
finally: