THRIFT-3651 Make backports.match_hostname and ipaddress optional
Client: Python
Patch: Nobuaki Sukegawa

This closes #880
diff --git a/lib/py/src/transport/TSSLSocket.py b/lib/py/src/transport/TSSLSocket.py
index 4cae27c..e57a0d4 100644
--- a/lib/py/src/transport/TSSLSocket.py
+++ b/lib/py/src/transport/TSSLSocket.py
@@ -23,8 +23,8 @@
 import ssl
 import sys
 import warnings
-from backports.ssl_match_hostname import match_hostname
 
+from .sslcompat import _match_hostname, _match_has_ipaddress
 from thrift.transport import TSocket
 from thrift.transport.TTransport import TTransportException
 
@@ -259,7 +259,7 @@
             kwargs['cert_reqs'] = ssl.CERT_REQUIRED if validate else ssl.CERT_NONE
 
         unix_socket = kwargs.pop('unix_socket', None)
-        self._validate_callback = kwargs.pop('validate_callback', match_hostname)
+        self._validate_callback = kwargs.pop('validate_callback', _match_hostname)
         TSSLBase.__init__(self, False, host, kwargs)
         TSocket.TSocket.__init__(self, host, port, unix_socket)
 
@@ -297,45 +297,6 @@
             except Exception as ex:
                 raise TTransportException(TTransportException.UNKNOWN, str(ex))
 
-    @staticmethod
-    def legacy_validate_callback(self, cert, hostname):
-        """legacy method to validate the peer's SSL certificate, and to check
-        the commonName of the certificate to ensure it matches the hostname we
-        used to make this connection.  Does not support subjectAltName records
-        in certificates.
-
-        raises TTransportException if the certificate fails validation.
-        """
-        if 'subject' not in cert:
-            raise TTransportException(
-                TTransportException.NOT_OPEN,
-                'No SSL certificate found from %s:%s' % (self.host, self.port))
-        fields = cert['subject']
-        for field in fields:
-            # ensure structure we get back is what we expect
-            if not isinstance(field, tuple):
-                continue
-            cert_pair = field[0]
-            if len(cert_pair) < 2:
-                continue
-            cert_key, cert_value = cert_pair[0:2]
-            if cert_key != 'commonName':
-                continue
-            certhost = cert_value
-            # this check should be performed by some sort of Access Manager
-            if certhost == hostname:
-                # success, cert commonName matches desired hostname
-                return
-            else:
-                raise TTransportException(
-                    TTransportException.UNKNOWN,
-                    'Hostname we connected to "%s" doesn\'t match certificate '
-                    'provided commonName "%s"' % (self.host, certhost))
-        raise TTransportException(
-            TTransportException.UNKNOWN,
-            'Could not validate SSL certificate from host "%s".  Cert=%s'
-            % (hostname, cert))
-
 
 class TSSLServerSocket(TSocket.TServerSocket, TSSLBase):
     """SSL implementation of TServerSocket
@@ -381,9 +342,12 @@
 
         unix_socket = kwargs.pop('unix_socket', None)
         self._validate_callback = \
-            kwargs.pop('validate_callback', match_hostname)
+            kwargs.pop('validate_callback', _match_hostname)
         TSSLBase.__init__(self, True, None, kwargs)
         TSocket.TServerSocket.__init__(self, host, port, unix_socket)
+        if self._should_verify and not _match_has_ipaddress:
+            raise ValueError('Need ipaddress and backports.ssl_match_hostname'
+                             'module to verify client certificate')
 
     def setCertfile(self, certfile):
         """Set or change the server certificate file used to wrap new