353 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			353 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
__ssl = __import__('ssl')
 | 
						|
 | 
						|
from eventlet.patcher import slurp_properties
 | 
						|
slurp_properties(__ssl, globals(), srckeys=dir(__ssl))
 | 
						|
 | 
						|
import sys
 | 
						|
import errno
 | 
						|
time = __import__('time')
 | 
						|
 | 
						|
from eventlet.support import get_errno, PY33, six
 | 
						|
from eventlet.hubs import trampoline, IOClosed
 | 
						|
from eventlet.greenio import (
 | 
						|
    set_nonblocking, GreenSocket, SOCKET_CLOSED, CONNECT_ERR, CONNECT_SUCCESS,
 | 
						|
)
 | 
						|
orig_socket = __import__('socket')
 | 
						|
socket = orig_socket.socket
 | 
						|
if sys.version_info >= (2, 7):
 | 
						|
    has_ciphers = True
 | 
						|
    timeout_exc = SSLError
 | 
						|
else:
 | 
						|
    has_ciphers = False
 | 
						|
    timeout_exc = orig_socket.timeout
 | 
						|
 | 
						|
__patched__ = ['SSLSocket', 'wrap_socket', 'sslwrap_simple']
 | 
						|
 | 
						|
_original_sslsocket = __ssl.SSLSocket
 | 
						|
 | 
						|
 | 
						|
class GreenSSLSocket(_original_sslsocket):
 | 
						|
    """ This is a green version of the SSLSocket class from the ssl module added
 | 
						|
    in 2.6.  For documentation on it, please see the Python standard
 | 
						|
    documentation.
 | 
						|
 | 
						|
    Python nonblocking ssl objects don't give errors when the other end
 | 
						|
    of the socket is closed (they do notice when the other end is shutdown,
 | 
						|
    though).  Any write/read operations will simply hang if the socket is
 | 
						|
    closed from the other end.  There is no obvious fix for this problem;
 | 
						|
    it appears to be a limitation of Python's ssl object implementation.
 | 
						|
    A workaround is to set a reasonable timeout on the socket using
 | 
						|
    settimeout(), and to close/reopen the connection when a timeout
 | 
						|
    occurs at an unexpected juncture in the code.
 | 
						|
    """
 | 
						|
    # we are inheriting from SSLSocket because its constructor calls
 | 
						|
    # do_handshake whose behavior we wish to override
 | 
						|
 | 
						|
    def __init__(self, sock, keyfile=None, certfile=None,
 | 
						|
                 server_side=False, cert_reqs=CERT_NONE,
 | 
						|
                 ssl_version=PROTOCOL_SSLv23, ca_certs=None,
 | 
						|
                 do_handshake_on_connect=True, *args, **kw):
 | 
						|
        if not isinstance(sock, GreenSocket):
 | 
						|
            sock = GreenSocket(sock)
 | 
						|
 | 
						|
        self.act_non_blocking = sock.act_non_blocking
 | 
						|
 | 
						|
        if six.PY2:
 | 
						|
            # On Python 2 SSLSocket constructor queries the timeout, it'd break without
 | 
						|
            # this assignment
 | 
						|
            self._timeout = sock.gettimeout()
 | 
						|
 | 
						|
        # nonblocking socket handshaking on connect got disabled so let's pretend it's disabled
 | 
						|
        # even when it's on
 | 
						|
        super(GreenSSLSocket, self).__init__(
 | 
						|
            sock.fd, keyfile, certfile, server_side, cert_reqs, ssl_version,
 | 
						|
            ca_certs, do_handshake_on_connect and six.PY2, *args, **kw)
 | 
						|
 | 
						|
        # the superclass initializer trashes the methods so we remove
 | 
						|
        # the local-object versions of them and let the actual class
 | 
						|
        # methods shine through
 | 
						|
        # Note: This for Python 2
 | 
						|
        try:
 | 
						|
            for fn in orig_socket._delegate_methods:
 | 
						|
                delattr(self, fn)
 | 
						|
        except AttributeError:
 | 
						|
            pass
 | 
						|
 | 
						|
        if six.PY3:
 | 
						|
            # Python 3 SSLSocket construction process overwrites the timeout so restore it
 | 
						|
            self._timeout = sock.gettimeout()
 | 
						|
 | 
						|
            # it also sets timeout to None internally apparently (tested with 3.4.2)
 | 
						|
            _original_sslsocket.settimeout(self, 0.0)
 | 
						|
            assert _original_sslsocket.gettimeout(self) == 0.0
 | 
						|
 | 
						|
            # see note above about handshaking
 | 
						|
            self.do_handshake_on_connect = do_handshake_on_connect
 | 
						|
            if do_handshake_on_connect and self._connected:
 | 
						|
                self.do_handshake()
 | 
						|
 | 
						|
    def settimeout(self, timeout):
 | 
						|
        self._timeout = timeout
 | 
						|
 | 
						|
    def gettimeout(self):
 | 
						|
        return self._timeout
 | 
						|
 | 
						|
    def setblocking(self, flag):
 | 
						|
        if flag:
 | 
						|
            self.act_non_blocking = False
 | 
						|
            self._timeout = None
 | 
						|
        else:
 | 
						|
            self.act_non_blocking = True
 | 
						|
            self._timeout = 0.0
 | 
						|
 | 
						|
    def _call_trampolining(self, func, *a, **kw):
 | 
						|
        if self.act_non_blocking:
 | 
						|
            return func(*a, **kw)
 | 
						|
        else:
 | 
						|
            while True:
 | 
						|
                try:
 | 
						|
                    return func(*a, **kw)
 | 
						|
                except SSLError as exc:
 | 
						|
                    if get_errno(exc) == SSL_ERROR_WANT_READ:
 | 
						|
                        trampoline(self,
 | 
						|
                                   read=True,
 | 
						|
                                   timeout=self.gettimeout(),
 | 
						|
                                   timeout_exc=timeout_exc('timed out'))
 | 
						|
                    elif get_errno(exc) == SSL_ERROR_WANT_WRITE:
 | 
						|
                        trampoline(self,
 | 
						|
                                   write=True,
 | 
						|
                                   timeout=self.gettimeout(),
 | 
						|
                                   timeout_exc=timeout_exc('timed out'))
 | 
						|
                    else:
 | 
						|
                        raise
 | 
						|
 | 
						|
    def write(self, data):
 | 
						|
        """Write DATA to the underlying SSL channel.  Returns
 | 
						|
        number of bytes of DATA actually transmitted."""
 | 
						|
        return self._call_trampolining(
 | 
						|
            super(GreenSSLSocket, self).write, data)
 | 
						|
 | 
						|
    def read(self, *args, **kwargs):
 | 
						|
        """Read up to LEN bytes and return them.
 | 
						|
        Return zero-length string on EOF."""
 | 
						|
        try:
 | 
						|
            return self._call_trampolining(
 | 
						|
                super(GreenSSLSocket, self).read, *args, **kwargs)
 | 
						|
        except IOClosed:
 | 
						|
            return b''
 | 
						|
 | 
						|
    def send(self, data, flags=0):
 | 
						|
        if self._sslobj:
 | 
						|
            return self._call_trampolining(
 | 
						|
                super(GreenSSLSocket, self).send, data, flags)
 | 
						|
        else:
 | 
						|
            trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
 | 
						|
            return socket.send(self, data, flags)
 | 
						|
 | 
						|
    def sendto(self, data, addr, flags=0):
 | 
						|
        # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
 | 
						|
        if self._sslobj:
 | 
						|
            raise ValueError("sendto not allowed on instances of %s" %
 | 
						|
                             self.__class__)
 | 
						|
        else:
 | 
						|
            trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
 | 
						|
            return socket.sendto(self, data, addr, flags)
 | 
						|
 | 
						|
    def sendall(self, data, flags=0):
 | 
						|
        # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
 | 
						|
        if self._sslobj:
 | 
						|
            if flags != 0:
 | 
						|
                raise ValueError(
 | 
						|
                    "non-zero flags not allowed in calls to sendall() on %s" %
 | 
						|
                    self.__class__)
 | 
						|
            amount = len(data)
 | 
						|
            count = 0
 | 
						|
            while (count < amount):
 | 
						|
                v = self.send(data[count:])
 | 
						|
                count += v
 | 
						|
                if v == 0:
 | 
						|
                    trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
 | 
						|
            return amount
 | 
						|
        else:
 | 
						|
            while True:
 | 
						|
                try:
 | 
						|
                    return socket.sendall(self, data, flags)
 | 
						|
                except orig_socket.error as e:
 | 
						|
                    if self.act_non_blocking:
 | 
						|
                        raise
 | 
						|
                    if get_errno(e) == errno.EWOULDBLOCK:
 | 
						|
                        trampoline(self, write=True,
 | 
						|
                                   timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out'))
 | 
						|
                    if get_errno(e) in SOCKET_CLOSED:
 | 
						|
                        return ''
 | 
						|
                    raise
 | 
						|
 | 
						|
    def recv(self, buflen=1024, flags=0):
 | 
						|
        # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
 | 
						|
        if self._sslobj:
 | 
						|
            if flags != 0:
 | 
						|
                raise ValueError(
 | 
						|
                    "non-zero flags not allowed in calls to recv() on %s" %
 | 
						|
                    self.__class__)
 | 
						|
            read = self.read(buflen)
 | 
						|
            return read
 | 
						|
        else:
 | 
						|
            while True:
 | 
						|
                try:
 | 
						|
                    return socket.recv(self, buflen, flags)
 | 
						|
                except orig_socket.error as e:
 | 
						|
                    if self.act_non_blocking:
 | 
						|
                        raise
 | 
						|
                    if get_errno(e) == errno.EWOULDBLOCK:
 | 
						|
                        try:
 | 
						|
                            trampoline(
 | 
						|
                                self, read=True,
 | 
						|
                                timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out'))
 | 
						|
                        except IOClosed:
 | 
						|
                            return b''
 | 
						|
                    if get_errno(e) in SOCKET_CLOSED:
 | 
						|
                        return b''
 | 
						|
                    raise
 | 
						|
 | 
						|
    def recv_into(self, buffer, nbytes=None, flags=0):
 | 
						|
        if not self.act_non_blocking:
 | 
						|
            trampoline(self, read=True, timeout=self.gettimeout(),
 | 
						|
                       timeout_exc=timeout_exc('timed out'))
 | 
						|
        return super(GreenSSLSocket, self).recv_into(buffer, nbytes, flags)
 | 
						|
 | 
						|
    def recvfrom(self, addr, buflen=1024, flags=0):
 | 
						|
        if not self.act_non_blocking:
 | 
						|
            trampoline(self, read=True, timeout=self.gettimeout(),
 | 
						|
                       timeout_exc=timeout_exc('timed out'))
 | 
						|
        return super(GreenSSLSocket, self).recvfrom(addr, buflen, flags)
 | 
						|
 | 
						|
    def recvfrom_into(self, buffer, nbytes=None, flags=0):
 | 
						|
        if not self.act_non_blocking:
 | 
						|
            trampoline(self, read=True, timeout=self.gettimeout(),
 | 
						|
                       timeout_exc=timeout_exc('timed out'))
 | 
						|
        return super(GreenSSLSocket, self).recvfrom_into(buffer, nbytes, flags)
 | 
						|
 | 
						|
    def unwrap(self):
 | 
						|
        return GreenSocket(self._call_trampolining(
 | 
						|
            super(GreenSSLSocket, self).unwrap))
 | 
						|
 | 
						|
    def do_handshake(self):
 | 
						|
        """Perform a TLS/SSL handshake."""
 | 
						|
        return self._call_trampolining(
 | 
						|
            super(GreenSSLSocket, self).do_handshake)
 | 
						|
 | 
						|
    def _socket_connect(self, addr):
 | 
						|
        real_connect = socket.connect
 | 
						|
        if self.act_non_blocking:
 | 
						|
            return real_connect(self, addr)
 | 
						|
        else:
 | 
						|
            # *NOTE: gross, copied code from greenio because it's not factored
 | 
						|
            # well enough to reuse
 | 
						|
            if self.gettimeout() is None:
 | 
						|
                while True:
 | 
						|
                    try:
 | 
						|
                        return real_connect(self, addr)
 | 
						|
                    except orig_socket.error as exc:
 | 
						|
                        if get_errno(exc) in CONNECT_ERR:
 | 
						|
                            trampoline(self, write=True)
 | 
						|
                        elif get_errno(exc) in CONNECT_SUCCESS:
 | 
						|
                            return
 | 
						|
                        else:
 | 
						|
                            raise
 | 
						|
            else:
 | 
						|
                end = time.time() + self.gettimeout()
 | 
						|
                while True:
 | 
						|
                    try:
 | 
						|
                        real_connect(self, addr)
 | 
						|
                    except orig_socket.error as exc:
 | 
						|
                        if get_errno(exc) in CONNECT_ERR:
 | 
						|
                            trampoline(
 | 
						|
                                self, write=True,
 | 
						|
                                timeout=end - time.time(), timeout_exc=timeout_exc('timed out'))
 | 
						|
                        elif get_errno(exc) in CONNECT_SUCCESS:
 | 
						|
                            return
 | 
						|
                        else:
 | 
						|
                            raise
 | 
						|
                    if time.time() >= end:
 | 
						|
                        raise timeout_exc('timed out')
 | 
						|
 | 
						|
    def connect(self, addr):
 | 
						|
        """Connects to remote ADDR, and then wraps the connection in
 | 
						|
        an SSL channel."""
 | 
						|
        # *NOTE: grrrrr copied this code from ssl.py because of the reference
 | 
						|
        # to socket.connect which we don't want to call directly
 | 
						|
        if self._sslobj:
 | 
						|
            raise ValueError("attempt to connect already-connected SSLSocket!")
 | 
						|
        self._socket_connect(addr)
 | 
						|
        server_side = False
 | 
						|
        try:
 | 
						|
            sslwrap = _ssl.sslwrap
 | 
						|
        except AttributeError:
 | 
						|
            # sslwrap was removed in 3.x and later in 2.7.9
 | 
						|
            if six.PY2:
 | 
						|
                sslobj = self._context._wrap_socket(self._sock, server_side, ssl_sock=self)
 | 
						|
            else:
 | 
						|
                context = self.context if PY33 else self._context
 | 
						|
                sslobj = context._wrap_socket(self, server_side)
 | 
						|
        else:
 | 
						|
            sslobj = sslwrap(self._sock, server_side, self.keyfile, self.certfile,
 | 
						|
                             self.cert_reqs, self.ssl_version,
 | 
						|
                             self.ca_certs, *([self.ciphers] if has_ciphers else []))
 | 
						|
 | 
						|
        self._sslobj = sslobj
 | 
						|
        if self.do_handshake_on_connect:
 | 
						|
            self.do_handshake()
 | 
						|
 | 
						|
    def accept(self):
 | 
						|
        """Accepts a new connection from a remote client, and returns
 | 
						|
        a tuple containing that new connection wrapped with a server-side
 | 
						|
        SSL channel, and the address of the remote client."""
 | 
						|
        # RDW grr duplication of code from greenio
 | 
						|
        if self.act_non_blocking:
 | 
						|
            newsock, addr = socket.accept(self)
 | 
						|
        else:
 | 
						|
            while True:
 | 
						|
                try:
 | 
						|
                    newsock, addr = socket.accept(self)
 | 
						|
                    set_nonblocking(newsock)
 | 
						|
                    break
 | 
						|
                except orig_socket.error as e:
 | 
						|
                    if get_errno(e) != errno.EWOULDBLOCK:
 | 
						|
                        raise
 | 
						|
                    trampoline(self, read=True, timeout=self.gettimeout(),
 | 
						|
                               timeout_exc=timeout_exc('timed out'))
 | 
						|
 | 
						|
        new_ssl = type(self)(
 | 
						|
            newsock,
 | 
						|
            keyfile=self.keyfile,
 | 
						|
            certfile=self.certfile,
 | 
						|
            server_side=True,
 | 
						|
            cert_reqs=self.cert_reqs,
 | 
						|
            ssl_version=self.ssl_version,
 | 
						|
            ca_certs=self.ca_certs,
 | 
						|
            do_handshake_on_connect=self.do_handshake_on_connect,
 | 
						|
            suppress_ragged_eofs=self.suppress_ragged_eofs)
 | 
						|
        return (new_ssl, addr)
 | 
						|
 | 
						|
    def dup(self):
 | 
						|
        raise NotImplementedError("Can't dup an ssl object")
 | 
						|
 | 
						|
SSLSocket = GreenSSLSocket
 | 
						|
 | 
						|
 | 
						|
def wrap_socket(sock, *a, **kw):
 | 
						|
    return GreenSSLSocket(sock, *a, **kw)
 | 
						|
 | 
						|
 | 
						|
if hasattr(__ssl, 'sslwrap_simple'):
 | 
						|
    def sslwrap_simple(sock, keyfile=None, certfile=None):
 | 
						|
        """A replacement for the old socket.ssl function.  Designed
 | 
						|
        for compability with Python 2.5 and earlier.  Will disappear in
 | 
						|
        Python 3.0."""
 | 
						|
        ssl_sock = GreenSSLSocket(sock, keyfile=keyfile, certfile=certfile,
 | 
						|
                                  server_side=False,
 | 
						|
                                  cert_reqs=CERT_NONE,
 | 
						|
                                  ssl_version=PROTOCOL_SSLv23,
 | 
						|
                                  ca_certs=None)
 | 
						|
        return ssl_sock
 |