From c2f1c4b981a173b3e8953ddc92b02d28df7c1959 Mon Sep 17 00:00:00 2001 From: Joakim Plate Date: Mon, 8 Jul 2019 11:33:23 +0200 Subject: [PATCH] Correct socket use in cert_expiry platform (#25011) * Make sure we use same family for ssl socket and connection getaddrinfo result could be different from what connection was made with. It also blocks potential use of happy eye balls algorithm This also fixes lingering sockets until python garbage collection. * Add availability value if unable to get expiry * Fix lint issue --- .../components/cert_expiry/sensor.py | 34 +++++++++++-------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/homeassistant/components/cert_expiry/sensor.py b/homeassistant/components/cert_expiry/sensor.py index 54ba378f91c..f1fff08755f 100644 --- a/homeassistant/components/cert_expiry/sensor.py +++ b/homeassistant/components/cert_expiry/sensor.py @@ -55,6 +55,7 @@ class SSLCertificate(Entity): self.server_port = server_port self._name = sensor_name self._state = None + self._available = False @property def name(self): @@ -76,34 +77,39 @@ class SSLCertificate(Entity): """Icon to use in the frontend, if any.""" return 'mdi:certificate' + @property + def available(self): + """Icon to use in the frontend, if any.""" + return self._available + def update(self): """Fetch the certificate information.""" + ctx = ssl.create_default_context() try: - ctx = ssl.create_default_context() - host_info = socket.getaddrinfo(self.server_name, self.server_port) - family = host_info[0][0] - sock = ctx.wrap_socket( - socket.socket(family=family), server_hostname=self.server_name) - sock.settimeout(TIMEOUT) - sock.connect((self.server_name, self.server_port)) + address = (self.server_name, self.server_port) + with socket.create_connection( + address, timeout=TIMEOUT) as sock: + with ctx.wrap_socket( + sock, server_hostname=address[0]) as ssock: + cert = ssock.getpeercert() + except socket.gaierror: _LOGGER.error("Cannot resolve hostname: %s", self.server_name) + self._available = False return except socket.timeout: _LOGGER.error( "Connection timeout with server: %s", self.server_name) + self._available = False return except OSError: - _LOGGER.error("Cannot connect to %s", self.server_name) - return - - try: - cert = sock.getpeercert() - except OSError: - _LOGGER.error("Cannot fetch certificate from %s", self.server_name) + _LOGGER.error("Cannot fetch certificate from %s", + self.server_name, exc_info=1) + self._available = False return ts_seconds = ssl.cert_time_to_seconds(cert['notAfter']) timestamp = datetime.fromtimestamp(ts_seconds) expiry = timestamp - datetime.today() + self._available = True self._state = expiry.days