Add python-eventlet 0.16.1
[packages/trusty/python-eventlet.git] / eventlet / eventlet / green / ssl.py
1 __ssl = __import__('ssl')
2
3 from eventlet.patcher import slurp_properties
4 slurp_properties(__ssl, globals(), srckeys=dir(__ssl))
5
6 import sys
7 import errno
8 time = __import__('time')
9
10 from eventlet.support import get_errno, PY33, six
11 from eventlet.hubs import trampoline, IOClosed
12 from eventlet.greenio import (
13     set_nonblocking, GreenSocket, SOCKET_CLOSED, CONNECT_ERR, CONNECT_SUCCESS,
14 )
15 orig_socket = __import__('socket')
16 socket = orig_socket.socket
17 if sys.version_info >= (2, 7):
18     has_ciphers = True
19     timeout_exc = SSLError
20 else:
21     has_ciphers = False
22     timeout_exc = orig_socket.timeout
23
24 __patched__ = ['SSLSocket', 'wrap_socket', 'sslwrap_simple']
25
26 _original_sslsocket = __ssl.SSLSocket
27
28
29 class GreenSSLSocket(_original_sslsocket):
30     """ This is a green version of the SSLSocket class from the ssl module added
31     in 2.6.  For documentation on it, please see the Python standard
32     documentation.
33
34     Python nonblocking ssl objects don't give errors when the other end
35     of the socket is closed (they do notice when the other end is shutdown,
36     though).  Any write/read operations will simply hang if the socket is
37     closed from the other end.  There is no obvious fix for this problem;
38     it appears to be a limitation of Python's ssl object implementation.
39     A workaround is to set a reasonable timeout on the socket using
40     settimeout(), and to close/reopen the connection when a timeout
41     occurs at an unexpected juncture in the code.
42     """
43     # we are inheriting from SSLSocket because its constructor calls
44     # do_handshake whose behavior we wish to override
45
46     def __init__(self, sock, keyfile=None, certfile=None,
47                  server_side=False, cert_reqs=CERT_NONE,
48                  ssl_version=PROTOCOL_SSLv23, ca_certs=None,
49                  do_handshake_on_connect=True, *args, **kw):
50         if not isinstance(sock, GreenSocket):
51             sock = GreenSocket(sock)
52
53         self.act_non_blocking = sock.act_non_blocking
54
55         if six.PY2:
56             # On Python 2 SSLSocket constructor queries the timeout, it'd break without
57             # this assignment
58             self._timeout = sock.gettimeout()
59
60         # nonblocking socket handshaking on connect got disabled so let's pretend it's disabled
61         # even when it's on
62         super(GreenSSLSocket, self).__init__(
63             sock.fd, keyfile, certfile, server_side, cert_reqs, ssl_version,
64             ca_certs, do_handshake_on_connect and six.PY2, *args, **kw)
65
66         # the superclass initializer trashes the methods so we remove
67         # the local-object versions of them and let the actual class
68         # methods shine through
69         # Note: This for Python 2
70         try:
71             for fn in orig_socket._delegate_methods:
72                 delattr(self, fn)
73         except AttributeError:
74             pass
75
76         if six.PY3:
77             # Python 3 SSLSocket construction process overwrites the timeout so restore it
78             self._timeout = sock.gettimeout()
79
80             # it also sets timeout to None internally apparently (tested with 3.4.2)
81             _original_sslsocket.settimeout(self, 0.0)
82             assert _original_sslsocket.gettimeout(self) == 0.0
83
84             # see note above about handshaking
85             self.do_handshake_on_connect = do_handshake_on_connect
86             if do_handshake_on_connect and self._connected:
87                 self.do_handshake()
88
89     def settimeout(self, timeout):
90         self._timeout = timeout
91
92     def gettimeout(self):
93         return self._timeout
94
95     def setblocking(self, flag):
96         if flag:
97             self.act_non_blocking = False
98             self._timeout = None
99         else:
100             self.act_non_blocking = True
101             self._timeout = 0.0
102
103     def _call_trampolining(self, func, *a, **kw):
104         if self.act_non_blocking:
105             return func(*a, **kw)
106         else:
107             while True:
108                 try:
109                     return func(*a, **kw)
110                 except SSLError as exc:
111                     if get_errno(exc) == SSL_ERROR_WANT_READ:
112                         trampoline(self,
113                                    read=True,
114                                    timeout=self.gettimeout(),
115                                    timeout_exc=timeout_exc('timed out'))
116                     elif get_errno(exc) == SSL_ERROR_WANT_WRITE:
117                         trampoline(self,
118                                    write=True,
119                                    timeout=self.gettimeout(),
120                                    timeout_exc=timeout_exc('timed out'))
121                     else:
122                         raise
123
124     def write(self, data):
125         """Write DATA to the underlying SSL channel.  Returns
126         number of bytes of DATA actually transmitted."""
127         return self._call_trampolining(
128             super(GreenSSLSocket, self).write, data)
129
130     def read(self, *args, **kwargs):
131         """Read up to LEN bytes and return them.
132         Return zero-length string on EOF."""
133         try:
134             return self._call_trampolining(
135                 super(GreenSSLSocket, self).read, *args, **kwargs)
136         except IOClosed:
137             return b''
138
139     def send(self, data, flags=0):
140         if self._sslobj:
141             return self._call_trampolining(
142                 super(GreenSSLSocket, self).send, data, flags)
143         else:
144             trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
145             return socket.send(self, data, flags)
146
147     def sendto(self, data, addr, flags=0):
148         # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
149         if self._sslobj:
150             raise ValueError("sendto not allowed on instances of %s" %
151                              self.__class__)
152         else:
153             trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
154             return socket.sendto(self, data, addr, flags)
155
156     def sendall(self, data, flags=0):
157         # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
158         if self._sslobj:
159             if flags != 0:
160                 raise ValueError(
161                     "non-zero flags not allowed in calls to sendall() on %s" %
162                     self.__class__)
163             amount = len(data)
164             count = 0
165             while (count < amount):
166                 v = self.send(data[count:])
167                 count += v
168                 if v == 0:
169                     trampoline(self, write=True, timeout_exc=timeout_exc('timed out'))
170             return amount
171         else:
172             while True:
173                 try:
174                     return socket.sendall(self, data, flags)
175                 except orig_socket.error as e:
176                     if self.act_non_blocking:
177                         raise
178                     if get_errno(e) == errno.EWOULDBLOCK:
179                         trampoline(self, write=True,
180                                    timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out'))
181                     if get_errno(e) in SOCKET_CLOSED:
182                         return ''
183                     raise
184
185     def recv(self, buflen=1024, flags=0):
186         # *NOTE: gross, copied code from ssl.py becase it's not factored well enough to be used as-is
187         if self._sslobj:
188             if flags != 0:
189                 raise ValueError(
190                     "non-zero flags not allowed in calls to recv() on %s" %
191                     self.__class__)
192             read = self.read(buflen)
193             return read
194         else:
195             while True:
196                 try:
197                     return socket.recv(self, buflen, flags)
198                 except orig_socket.error as e:
199                     if self.act_non_blocking:
200                         raise
201                     if get_errno(e) == errno.EWOULDBLOCK:
202                         try:
203                             trampoline(
204                                 self, read=True,
205                                 timeout=self.gettimeout(), timeout_exc=timeout_exc('timed out'))
206                         except IOClosed:
207                             return b''
208                     if get_errno(e) in SOCKET_CLOSED:
209                         return b''
210                     raise
211
212     def recv_into(self, buffer, nbytes=None, flags=0):
213         if not self.act_non_blocking:
214             trampoline(self, read=True, timeout=self.gettimeout(),
215                        timeout_exc=timeout_exc('timed out'))
216         return super(GreenSSLSocket, self).recv_into(buffer, nbytes, flags)
217
218     def recvfrom(self, addr, buflen=1024, flags=0):
219         if not self.act_non_blocking:
220             trampoline(self, read=True, timeout=self.gettimeout(),
221                        timeout_exc=timeout_exc('timed out'))
222         return super(GreenSSLSocket, self).recvfrom(addr, buflen, flags)
223
224     def recvfrom_into(self, buffer, nbytes=None, flags=0):
225         if not self.act_non_blocking:
226             trampoline(self, read=True, timeout=self.gettimeout(),
227                        timeout_exc=timeout_exc('timed out'))
228         return super(GreenSSLSocket, self).recvfrom_into(buffer, nbytes, flags)
229
230     def unwrap(self):
231         return GreenSocket(self._call_trampolining(
232             super(GreenSSLSocket, self).unwrap))
233
234     def do_handshake(self):
235         """Perform a TLS/SSL handshake."""
236         return self._call_trampolining(
237             super(GreenSSLSocket, self).do_handshake)
238
239     def _socket_connect(self, addr):
240         real_connect = socket.connect
241         if self.act_non_blocking:
242             return real_connect(self, addr)
243         else:
244             # *NOTE: gross, copied code from greenio because it's not factored
245             # well enough to reuse
246             if self.gettimeout() is None:
247                 while True:
248                     try:
249                         return real_connect(self, addr)
250                     except orig_socket.error as exc:
251                         if get_errno(exc) in CONNECT_ERR:
252                             trampoline(self, write=True)
253                         elif get_errno(exc) in CONNECT_SUCCESS:
254                             return
255                         else:
256                             raise
257             else:
258                 end = time.time() + self.gettimeout()
259                 while True:
260                     try:
261                         real_connect(self, addr)
262                     except orig_socket.error as exc:
263                         if get_errno(exc) in CONNECT_ERR:
264                             trampoline(
265                                 self, write=True,
266                                 timeout=end - time.time(), timeout_exc=timeout_exc('timed out'))
267                         elif get_errno(exc) in CONNECT_SUCCESS:
268                             return
269                         else:
270                             raise
271                     if time.time() >= end:
272                         raise timeout_exc('timed out')
273
274     def connect(self, addr):
275         """Connects to remote ADDR, and then wraps the connection in
276         an SSL channel."""
277         # *NOTE: grrrrr copied this code from ssl.py because of the reference
278         # to socket.connect which we don't want to call directly
279         if self._sslobj:
280             raise ValueError("attempt to connect already-connected SSLSocket!")
281         self._socket_connect(addr)
282         server_side = False
283         try:
284             sslwrap = _ssl.sslwrap
285         except AttributeError:
286             # sslwrap was removed in 3.x and later in 2.7.9
287             if six.PY2:
288                 sslobj = self._context._wrap_socket(self._sock, server_side, ssl_sock=self)
289             else:
290                 context = self.context if PY33 else self._context
291                 sslobj = context._wrap_socket(self, server_side)
292         else:
293             sslobj = sslwrap(self._sock, server_side, self.keyfile, self.certfile,
294                              self.cert_reqs, self.ssl_version,
295                              self.ca_certs, *([self.ciphers] if has_ciphers else []))
296
297         self._sslobj = sslobj
298         if self.do_handshake_on_connect:
299             self.do_handshake()
300
301     def accept(self):
302         """Accepts a new connection from a remote client, and returns
303         a tuple containing that new connection wrapped with a server-side
304         SSL channel, and the address of the remote client."""
305         # RDW grr duplication of code from greenio
306         if self.act_non_blocking:
307             newsock, addr = socket.accept(self)
308         else:
309             while True:
310                 try:
311                     newsock, addr = socket.accept(self)
312                     set_nonblocking(newsock)
313                     break
314                 except orig_socket.error as e:
315                     if get_errno(e) != errno.EWOULDBLOCK:
316                         raise
317                     trampoline(self, read=True, timeout=self.gettimeout(),
318                                timeout_exc=timeout_exc('timed out'))
319
320         new_ssl = type(self)(
321             newsock,
322             keyfile=self.keyfile,
323             certfile=self.certfile,
324             server_side=True,
325             cert_reqs=self.cert_reqs,
326             ssl_version=self.ssl_version,
327             ca_certs=self.ca_certs,
328             do_handshake_on_connect=self.do_handshake_on_connect,
329             suppress_ragged_eofs=self.suppress_ragged_eofs)
330         return (new_ssl, addr)
331
332     def dup(self):
333         raise NotImplementedError("Can't dup an ssl object")
334
335 SSLSocket = GreenSSLSocket
336
337
338 def wrap_socket(sock, *a, **kw):
339     return GreenSSLSocket(sock, *a, **kw)
340
341
342 if hasattr(__ssl, 'sslwrap_simple'):
343     def sslwrap_simple(sock, keyfile=None, certfile=None):
344         """A replacement for the old socket.ssl function.  Designed
345         for compability with Python 2.5 and earlier.  Will disappear in
346         Python 3.0."""
347         ssl_sock = GreenSSLSocket(sock, keyfile=keyfile, certfile=certfile,
348                                   server_side=False,
349                                   cert_reqs=CERT_NONE,
350                                   ssl_version=PROTOCOL_SSLv23,
351                                   ca_certs=None)
352         return ssl_sock