Initial import to Tizen
[profile/ivi/python-twisted.git] / twisted / internet / _oldtls.py
1 # -*- test-case-name: twisted.test.test_ssl -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 """
6 This module implements OpenSSL socket BIO based TLS support.  It is only used if
7 memory BIO APIs are not available, which is when the version of pyOpenSSL
8 installed is older than 0.10 (when L{twisted.protocols.tls} is not importable).
9 This implementation is undesirable because of the complexity of working with
10 OpenSSL's non-blocking socket-based APIs (which this module probably does about
11 99% correctly, but see #4455 for an example of a problem with it).
12
13 Support for older versions of pyOpenSSL is now deprecated and will be removed
14 (see #5014).
15
16 @see: L{twisted.internet._newtls}
17 @since: 11.1
18 """
19
20 import os, warnings
21
22 from twisted.python.runtime import platformType
23 if platformType == 'win32':
24     from errno import WSAEINTR as EINTR
25     from errno import WSAEWOULDBLOCK as EWOULDBLOCK
26     from errno import WSAENOBUFS as ENOBUFS
27 else:
28     from errno import EINTR
29     from errno import EWOULDBLOCK
30     from errno import ENOBUFS
31
32 from OpenSSL import SSL, __version__ as _sslversion
33
34 from zope.interface import implements
35
36 from twisted.python import log
37 from twisted.internet.interfaces import ITLSTransport, ISSLTransport
38 from twisted.internet.abstract import FileDescriptor
39 from twisted.internet.main import CONNECTION_DONE, CONNECTION_LOST
40 from twisted.internet._ssl import _TLSDelayed
41
42 warnings.warn(
43     "Support for pyOpenSSL %s is deprecated.  "
44     "Upgrade to pyOpenSSL 0.10 or newer." % (_sslversion,),
45     category=DeprecationWarning,
46     stacklevel=100)
47
48 class _TLSMixin:
49     _socketShutdownMethod = 'sock_shutdown'
50
51     writeBlockedOnRead = 0
52     readBlockedOnWrite = 0
53     _userWantRead = _userWantWrite = True
54
55     def getPeerCertificate(self):
56         return self.socket.get_peer_certificate()
57
58     def doRead(self):
59         if self.disconnected:
60             # See the comment in the similar check in doWrite below.
61             # Additionally, in order for anything other than returning
62             # CONNECTION_DONE here to make sense, it will probably be necessary
63             # to implement a way to switch back to TCP from TLS (actually, if
64             # we did something other than return CONNECTION_DONE, that would be
65             # a big part of implementing that feature).  In other words, the
66             # expectation is that doRead will be called when self.disconnected
67             # is True only when the connection has been lost.  It's possible
68             # that the other end could stop speaking TLS and then send us some
69             # non-TLS data.  We'll end up ignoring that data and dropping the
70             # connection.  There's no unit tests for this check in the cases
71             # where it makes a difference.  The test suite only hits this
72             # codepath when it would have otherwise hit the SSL.ZeroReturnError
73             # exception handler below, which has exactly the same behavior as
74             # this conditional.  Maybe that's the only case that can ever be
75             # triggered, I'm not sure.  -exarkun
76             return CONNECTION_DONE
77         if self.writeBlockedOnRead:
78             self.writeBlockedOnRead = 0
79             self._resetReadWrite()
80         try:
81             return self._base.doRead(self)
82         except SSL.ZeroReturnError:
83             return CONNECTION_DONE
84         except SSL.WantReadError:
85             return
86         except SSL.WantWriteError:
87             self.readBlockedOnWrite = 1
88             self._base.startWriting(self)
89             self._base.stopReading(self)
90             return
91         except SSL.SysCallError, (retval, desc):
92             if ((retval == -1 and desc == 'Unexpected EOF')
93                 or retval > 0):
94                 return CONNECTION_LOST
95             log.err()
96             return CONNECTION_LOST
97         except SSL.Error, e:
98             return e
99
100     def doWrite(self):
101         # Retry disconnecting
102         if self.disconnected:
103             # This case is triggered when "disconnected" is set to True by a
104             # call to _postLoseConnection from FileDescriptor.doWrite (to which
105             # we upcall at the end of this overridden version of that API).  It
106             # means that while, as far as any protocol connected to this
107             # transport is concerned, the connection no longer exists, the
108             # connection *does* actually still exist.  Instead of closing the
109             # connection in the overridden _postLoseConnection, we probably
110             # tried (and failed) to send a TLS close alert.  The TCP connection
111             # is still up and we're waiting for the socket to become writeable
112             # enough for the TLS close alert to actually be sendable.  Only
113             # then will the connection actually be torn down. -exarkun
114             return self._postLoseConnection()
115         if self._writeDisconnected:
116             return self._closeWriteConnection()
117
118         if self.readBlockedOnWrite:
119             self.readBlockedOnWrite = 0
120             self._resetReadWrite()
121         return self._base.doWrite(self)
122
123     def writeSomeData(self, data):
124         try:
125             return self._base.writeSomeData(self, data)
126         except SSL.WantWriteError:
127             return 0
128         except SSL.WantReadError:
129             self.writeBlockedOnRead = 1
130             self._base.stopWriting(self)
131             self._base.startReading(self)
132             return 0
133         except SSL.ZeroReturnError:
134             return CONNECTION_LOST
135         except SSL.SysCallError, e:
136             if e[0] == -1 and data == "":
137                 # errors when writing empty strings are expected
138                 # and can be ignored
139                 return 0
140             else:
141                 return CONNECTION_LOST
142         except SSL.Error, e:
143             return e
144
145
146     def _postLoseConnection(self):
147         """
148         Gets called after loseConnection(), after buffered data is sent.
149
150         We try to send an SSL shutdown alert, but if it doesn't work, retry
151         when the socket is writable.
152         """
153         # Here, set "disconnected" to True to trick higher levels into thinking
154         # the connection is really gone.  It's not, and we're not going to
155         # close it yet.  Instead, we'll try to send a TLS close alert to shut
156         # down the TLS connection cleanly.  Only after we actually get the
157         # close alert into the socket will we disconnect the underlying TCP
158         # connection.
159         self.disconnected = True
160         if hasattr(self.socket, 'set_shutdown'):
161             # If possible, mark the state of the TLS connection as having
162             # already received a TLS close alert from the peer.  Why do
163             # this???
164             self.socket.set_shutdown(SSL.RECEIVED_SHUTDOWN)
165         return self._sendCloseAlert()
166
167
168     def _sendCloseAlert(self):
169         # Okay, *THIS* is a bit complicated.
170
171         # Basically, the issue is, OpenSSL seems to not actually return
172         # errors from SSL_shutdown. Therefore, the only way to
173         # determine if the close notification has been sent is by
174         # SSL_shutdown returning "done". However, it will not claim it's
175         # done until it's both sent *and* received a shutdown notification.
176
177         # I don't actually want to wait for a received shutdown
178         # notification, though, so, I have to set RECEIVED_SHUTDOWN
179         # before calling shutdown. Then, it'll return True once it's
180         # *SENT* the shutdown.
181
182         # However, RECEIVED_SHUTDOWN can't be left set, because then
183         # reads will fail, breaking half close.
184
185         # Also, since shutdown doesn't report errors, an empty write call is
186         # done first, to try to detect if the connection has gone away.
187         # (*NOT* an SSL_write call, because that fails once you've called
188         # shutdown)
189         try:
190             os.write(self.socket.fileno(), '')
191         except OSError, se:
192             if se.args[0] in (EINTR, EWOULDBLOCK, ENOBUFS):
193                 return 0
194             # Write error, socket gone
195             return CONNECTION_LOST
196
197         try:
198             if hasattr(self.socket, 'set_shutdown'):
199                 laststate = self.socket.get_shutdown()
200                 self.socket.set_shutdown(laststate | SSL.RECEIVED_SHUTDOWN)
201                 done = self.socket.shutdown()
202                 if not (laststate & SSL.RECEIVED_SHUTDOWN):
203                     self.socket.set_shutdown(SSL.SENT_SHUTDOWN)
204             else:
205                 #warnings.warn("SSL connection shutdown possibly unreliable, "
206                 #              "please upgrade to ver 0.XX", category=UserWarning)
207                 self.socket.shutdown()
208                 done = True
209         except SSL.Error, e:
210             return e
211
212         if done:
213             self.stopWriting()
214             # Note that this is tested for by identity below.
215             return CONNECTION_DONE
216         else:
217             # For some reason, the close alert wasn't sent.  Start writing
218             # again so that we'll get another chance to send it.
219             self.startWriting()
220             # On Linux, select will sometimes not report a closed file
221             # descriptor in the write set (in particular, it seems that if a
222             # send() fails with EPIPE, the socket will not appear in the write
223             # set).  The shutdown call above (which calls down to SSL_shutdown)
224             # may have swallowed a write error.  Therefore, also start reading
225             # so that if the socket is closed we will notice.  This doesn't
226             # seem to be a problem for poll (because poll reports errors
227             # separately) or with select on BSD (presumably because, unlike
228             # Linux, it doesn't implement select in terms of poll and then map
229             # POLLHUP to select's in fd_set).
230             self.startReading()
231             return None
232
233     def _closeWriteConnection(self):
234         result = self._sendCloseAlert()
235
236         if result is CONNECTION_DONE:
237             return self._base._closeWriteConnection(self)
238
239         return result
240
241     def startReading(self):
242         self._userWantRead = True
243         if not self.readBlockedOnWrite:
244             return self._base.startReading(self)
245
246
247     def stopReading(self):
248         self._userWantRead = False
249         # If we've disconnected, preventing stopReading() from happening
250         # because we are blocked on a read is silly; the read will never
251         # happen.
252         if self.disconnected or not self.writeBlockedOnRead:
253             return self._base.stopReading(self)
254
255
256     def startWriting(self):
257         self._userWantWrite = True
258         if not self.writeBlockedOnRead:
259             return self._base.startWriting(self)
260
261
262     def stopWriting(self):
263         self._userWantWrite = False
264         # If we've disconnected, preventing stopWriting() from happening
265         # because we are blocked on a write is silly; the write will never
266         # happen.
267         if self.disconnected or not self.readBlockedOnWrite:
268             return self._base.stopWriting(self)
269
270
271     def _resetReadWrite(self):
272         # After changing readBlockedOnWrite or writeBlockedOnRead,
273         # call this to reset the state to what the user requested.
274         if self._userWantWrite:
275             self.startWriting()
276         else:
277             self.stopWriting()
278
279         if self._userWantRead:
280             self.startReading()
281         else:
282             self.stopReading()
283
284
285
286 def _getTLSClass(klass, _existing={}):
287     if klass not in _existing:
288         class TLSConnection(_TLSMixin, klass):
289             implements(ISSLTransport)
290             _base = klass
291         _existing[klass] = TLSConnection
292     return _existing[klass]
293
294
295 class ConnectionMixin(object):
296     """
297     Mixin for L{twisted.internet.tcp.Connection} to help implement
298     L{ITLSTransport} using pyOpenSSL to do crypto and I/O.
299     """
300     TLS = 0
301
302     _tlsWaiting = None
303     def startTLS(self, ctx, extra=True):
304         assert not self.TLS
305         if self.dataBuffer or self._tempDataBuffer:
306             # pre-TLS bytes are still being written.  Starting TLS now
307             # will do the wrong thing.  Instead, mark that we're trying
308             # to go into the TLS state.
309             self._tlsWaiting = _TLSDelayed([], ctx, extra)
310             return False
311
312         self.stopReading()
313         self.stopWriting()
314         self._startTLS()
315         self.socket = SSL.Connection(ctx.getContext(), self.socket)
316         self.fileno = self.socket.fileno
317         self.startReading()
318         return True
319
320
321     def _startTLS(self):
322         self.TLS = 1
323         self.__class__ = _getTLSClass(self.__class__)
324
325
326     def write(self, bytes):
327         if self._tlsWaiting is not None:
328             self._tlsWaiting.bufferedData.append(bytes)
329         else:
330             FileDescriptor.write(self, bytes)
331
332
333     def writeSequence(self, iovec):
334         if self._tlsWaiting is not None:
335             self._tlsWaiting.bufferedData.extend(iovec)
336         else:
337             FileDescriptor.writeSequence(self, iovec)
338
339
340     def doWrite(self):
341         result = FileDescriptor.doWrite(self)
342         if self._tlsWaiting is not None:
343             if not self.dataBuffer and not self._tempDataBuffer:
344                 waiting = self._tlsWaiting
345                 self._tlsWaiting = None
346                 self.startTLS(waiting.context, waiting.extra)
347                 self.writeSequence(waiting.bufferedData)
348         return result
349
350
351
352 class ClientMixin(object):
353     """
354     Mixin for L{twisted.internet.tcp.Client} to implement the client part of
355     L{ITLSTransport}.
356     """
357     implements(ITLSTransport)
358
359     def startTLS(self, ctx, client=1):
360         if self._base.startTLS(self, ctx, client):
361             if client:
362                 self.socket.set_connect_state()
363             else:
364                 self.socket.set_accept_state()
365
366
367
368 class ServerMixin(object):
369     """
370     Mixin for L{twisted.internet.tcp.Client} to implement the server part of
371     L{ITLSTransport}.
372     """
373     implements(ITLSTransport)
374
375     def startTLS(self, ctx, server=1):
376         if self._base.startTLS(self, ctx, server):
377             if server:
378                 self.socket.set_accept_state()
379             else:
380                 self.socket.set_connect_state()
381