diff sat/core/xmpp.py @ 2687:e9cd473a2f46

core (xmpp): server certificate validation: XMPP server certificate is now checked, and connection is refused (by default) if it's not valid. Certificate check can be disabled in the new parameter "Configuration/check_certificate". If certificate checking is disabled, a warning note is sent on every new connection. Twisted and Wokkel are temporarly monkey patched in sat.core.tls_patches module, until modifications are merged upstream.
author Goffi <goffi@goffi.org>
date Sat, 10 Nov 2018 10:16:35 +0100
parents 661f66d41215
children 943e78e18882
line wrap: on
line diff
--- a/sat/core/xmpp.py	Fri Nov 09 16:17:45 2018 +0100
+++ b/sat/core/xmpp.py	Sat Nov 10 10:16:35 2018 +0100
@@ -35,6 +35,7 @@
 log = getLogger(__name__)
 from sat.core import exceptions
 from sat.memory import encryption
+from sat.tools import xml_tools
 from zope.interface import implements
 import time
 import calendar
@@ -44,6 +45,7 @@
 
 class SatXMPPEntity(object):
     """Common code for Client and Component"""
+    _reason = None  # reason of disconnection
 
     def __init__(self, host_app, profile, max_retries):
 
@@ -214,6 +216,11 @@
             return
         super(SatXMPPEntity, self)._authd(xmlstream)
 
+        if self._reason is not None:
+            # if we have had trouble to connect we can reset
+            # the exception as the connection is now working.
+            del self._reason
+
         # the following Deferred is used to know when we are connected
         # so we need to be set it to None when connection is lost
         self._connected = defer.Deferred()
@@ -267,6 +274,11 @@
 
     ## connection ##
 
+    def _disconnected(self, reason):
+        # we have to save the reason of disconnection, otherwise it would be lost
+        self._reason = reason
+        super(SatXMPPEntity, self)._disconnected(reason)
+
     def connectionLost(self, connector, reason):
         try:
             self.keep_alife.stop()
@@ -288,9 +300,21 @@
         if not self.conn_deferred.called:
             # FIXME: real error is not gotten here (e.g. if jid is not know by Prosody,
             #        we should have the real error)
-            self.conn_deferred.errback(
-                error.StreamError(u"Server unexpectedly closed the connection")
-            )
+            if self._reason is None:
+                err = error.StreamError(u"Server unexpectedly closed the connection")
+            else:
+                err = self._reason
+                try:
+                    if err.value.args[0][0][2] == "certificate verify failed":
+                        err = exceptions.InvalidCertificate(
+                            _(u"Your server certificate is not valid "
+                              u"(its identity can't be checked).\n\n"
+                              u"This should never happen and may indicate that "
+                              u"somebody is trying to spy on you.\n"
+                              u"Please contact your server administrator."))
+                except (IndexError, TypeError):
+                    pass
+            self.conn_deferred.errback(err)
 
     @defer.inlineCallbacks
     def _cleanConnection(self, __):
@@ -544,16 +568,8 @@
     trigger_suffix = ""
     is_component = False
 
-    def __init__(
-        self,
-        host_app,
-        profile,
-        user_jid,
-        password,
-        host=None,
-        port=C.XMPP_C2S_PORT,
-        max_retries=C.XMPP_MAX_RETRIES,
-    ):
+    def __init__(self, host_app, profile, user_jid, password, host=None,
+                 port=C.XMPP_C2S_PORT, max_retries=C.XMPP_MAX_RETRIES):
         # XXX: DNS SRV records are checked when the host is not specified.
         # If no SRV record is found, the host is directly extracted from the JID.
         self.started = time.time()
@@ -594,11 +610,24 @@
                     .format(host_ori=user_jid.host, host=host, port=port)
                 )
 
+        self.check_certificate = host_app.memory.getParamA(
+            "check_certificate", "Connection", profile_key=profile)
+
         wokkel_client.XMPPClient.__init__(
-            self, user_jid, password, host or None, port or C.XMPP_C2S_PORT
+            self, user_jid, password, host or None, port or C.XMPP_C2S_PORT,
+            check_certificate = self.check_certificate
         )
         SatXMPPEntity.__init__(self, host_app, profile, max_retries)
 
+        if not self.check_certificate:
+            msg = (_(u"Certificate validation is deactivated, this is unsecure and "
+                u"somebody may be spying on you. If you have no good reason to disable "
+                u"certificate validation, please activate \"Check certificate\" in your "
+                u"settings in \"Connection\" tab."))
+            xml_tools.quickNote(host_app, self, msg, _(u"Security notice"),
+                level = C.XMLUI_DATA_LVL_WARNING)
+
+
     def _getPluginsList(self):
         for p in self.host_app.plugins.itervalues():
             if C.PLUG_MODE_CLIENT in p._info[u"modes"]: