Ticket #5014: t5014.diff

File t5014.diff, 18.3 KB (added by Alex Gaynor, 8 years ago)
  • deleted file twisted/internet/_oldtls.py

    diff --git a/twisted/internet/_oldtls.py b/twisted/internet/_oldtls.py
    deleted file mode 100644
    index e0d2cad..0000000
    + -  
    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 
  • twisted/internet/tcp.py

    diff --git a/twisted/internet/tcp.py b/twisted/internet/tcp.py
    index 2c68929..9cbb2c6 100644
    a b try: 
    3131        ClientMixin as _TLSClientMixin,
    3232        ServerMixin as _TLSServerMixin)
    3333except ImportError:
    34     try:
    35         if _PY3:
    36             # We're never going to port the old SSL code to Python 3:
    37             raise
    38         # Try to get the socket BIO based startTLS implementation, available in
    39         # all pyOpenSSL versions
    40         from twisted.internet._oldtls import (
    41             ConnectionMixin as _TLSConnectionMixin,
    42             ClientMixin as _TLSClientMixin,
    43             ServerMixin as _TLSServerMixin)
    44     except ImportError:
    45         # There is no version of startTLS available
    46         class _TLSConnectionMixin(object):
    47             TLS = False
    48         class _TLSClientMixin(object):
    49             pass
    50         class _TLSServerMixin(object):
    51             pass
     34    # There is no version of startTLS available
     35    class _TLSConnectionMixin(object):
     36        TLS = False
     37    class _TLSClientMixin(object):
     38        pass
     39    class _TLSServerMixin(object):
     40        pass
     41
    5242
    5343if platformType == 'win32':
    5444    # no such thing as WSAEPERM or error code 10001 according to winsock.h or MSDN
  • twisted/internet/test/test_tls.py

    diff --git a/twisted/internet/test/test_tls.py b/twisted/internet/test/test_tls.py
    index 5bd081a..7a71632 100644
    a b class AbortSSLConnectionTest(ReactorBuilder, AbortConnectionMixin, ContextGenera 
    388388            raise SkipTest("OpenSSL not available.")
    389389
    390390globals().update(AbortSSLConnectionTest.makeTestCaseClasses())
    391 
    392 class OldTLSDeprecationTest(TestCase):
    393     """
    394     Tests for the deprecation of L{twisted.internet._oldtls}, the implementation
    395     module for L{IReactorSSL} used when only an old version of pyOpenSSL is
    396     available.
    397     """
    398     if _PY3:
    399         skip = "_oldtls not supported on Python 3."
    400 
    401     def test_warning(self):
    402         """
    403         The use of L{twisted.internet._oldtls} is deprecated, and emits a
    404         L{DeprecationWarning}.
    405         """
    406         # Since _oldtls depends on OpenSSL, just skip this test if it isn't
    407         # installed on the system.  Faking it would be error prone.
    408         try:
    409             import OpenSSL
    410         except ImportError:
    411             raise SkipTest("OpenSSL not available.")
    412 
    413         # Change the apparent version of OpenSSL to one support for which is
    414         # deprecated.  And have it change back again after the test.
    415         self.patch(OpenSSL, '__version__', '0.5')
    416 
    417         # If the module was already imported, the import statement below won't
    418         # execute its top-level code.  Take it out of sys.modules so the import
    419         # system re-evaluates it.  Arrange to put the original back afterwards.
    420         # Also handle the case where it hasn't yet been imported.
    421         try:
    422             oldtls = sys.modules['twisted.internet._oldtls']
    423         except KeyError:
    424             self.addCleanup(sys.modules.pop, 'twisted.internet._oldtls')
    425         else:
    426             del sys.modules['twisted.internet._oldtls']
    427             self.addCleanup(
    428                 operator.setitem, sys.modules, 'twisted.internet._oldtls',
    429                 oldtls)
    430 
    431         # The actual test.
    432         import twisted.internet._oldtls
    433         warnings = self.flushWarnings()
    434         self.assertEqual(warnings[0]['category'], DeprecationWarning)
    435         self.assertEqual(
    436             warnings[0]['message'],
    437             "Support for pyOpenSSL 0.5 is deprecated.  "
    438             "Upgrade to pyOpenSSL 0.10 or newer.")
  • new file twisted/topfiles/5014.remove

    diff --git a/twisted/topfiles/5014.remove b/twisted/topfiles/5014.remove
    new file mode 100644
    index 0000000..dbe5cff
    - +  
     1Removed the deprecated twisted.internet._oldtls.