Implemented SSL support for IRC.

Sun, 02 Dec 2012 15:35:18 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 02 Dec 2012 15:35:18 +0100
changeset 2241
030924019d88
parent 2240
11445430c553
child 2242
3fae9858ea69

Implemented SSL support for IRC.

Network/IRC/IrcNetworkEditDialog.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkListDialog.py file | annotate | diff | comparison | revisions
Network/IRC/IrcNetworkManager.py file | annotate | diff | comparison | revisions
Network/IRC/IrcServerEditDialog.py file | annotate | diff | comparison | revisions
Network/IRC/IrcWidget.py file | annotate | diff | comparison | revisions
--- a/Network/IRC/IrcNetworkEditDialog.py	Sun Dec 02 13:48:01 2012 +0100
+++ b/Network/IRC/IrcNetworkEditDialog.py	Sun Dec 02 15:35:18 2012 +0100
@@ -16,7 +16,7 @@
 
 from .Ui_IrcNetworkEditDialog import Ui_IrcNetworkEditDialog
 
-from .IrcNetworkManager import IrcIdentity, IrcChannel
+from .IrcNetworkManager import IrcIdentity, IrcNetwork, IrcChannel
 from .IrcChannelEditDialog import IrcChannelEditDialog
 from .IrcServerEditDialog import IrcServerEditDialog
 from .IrcIdentitiesEditDialog import IrcIdentitiesEditDialog
@@ -49,8 +49,10 @@
         
         self.__okButton = self.buttonBox.button(QDialogButtonBox.Ok)
         
-        # TODO: add the ADD mode
-        self.__network = copy.deepcopy(self.__manager.getNetwork(networkName))
+        if networkName:
+            self.__network = copy.deepcopy(self.__manager.getNetwork(networkName))
+        else:
+            self.__network = IrcNetwork("")
         
         # network name
         self.networkEdit.setText(networkName)
@@ -132,6 +134,15 @@
         dlg.exec_()
         self.__refreshIdentityCombo(currentIdentity)
     
+    @pyqtSlot(str)
+    def on_serverEdit_textChanged(self, txt):
+        """
+        Private slot to handle changes of the server name.
+        
+        @param txt text entered into the server name edit (string)
+        """
+        self.__updateOkButton()
+    
     @pyqtSlot()
     def on_editServerButton_clicked(self):
         """
--- a/Network/IRC/IrcNetworkListDialog.py	Sun Dec 02 13:48:01 2012 +0100
+++ b/Network/IRC/IrcNetworkListDialog.py	Sun Dec 02 15:35:18 2012 +0100
@@ -123,10 +123,13 @@
     @pyqtSlot()
     def on_newButton_clicked(self):
         """
-        Slot documentation goes here.
+        Private slot to add a new network entry.
         """
-        # TODO: not implemented yet
-        raise NotImplementedError
+        dlg = IrcNetworkEditDialog(self.__manager, "", self)
+        if dlg.exec_() == QDialog.Accepted:
+            network = dlg.getNetwork()
+            self.__manager.addNetwork(network)
+            self.__refreshNetworksList()
     
     @pyqtSlot()
     def on_editButton_clicked(self):
--- a/Network/IRC/IrcNetworkManager.py	Sun Dec 02 13:48:01 2012 +0100
+++ b/Network/IRC/IrcNetworkManager.py	Sun Dec 02 15:35:18 2012 +0100
@@ -233,6 +233,7 @@
     Class implementing the IRC identity object.
     """
     DefaultPort = 6667
+    DefaultSslPort = 6697
     
     def __init__(self, name):
         """
@@ -616,21 +617,29 @@
         return self.__autoConnect
     
     @classmethod
-    def createDefaultNetwork(cls):
+    def createDefaultNetwork(cls, ssl=False):
         """
         Class method to create the default network.
         
+        @param ssl flag indicating to create a SSL network configuration (boolean)
         @return default network object (IrcNetwork)
         """
         # network
-        networkName = "Freenode"
+        if ssl:
+            networkName = "Freenode (SSL)"
+        else:
+            networkName = "Freenode"
         network = IrcNetwork(networkName)
         network.setIdentityName(IrcIdentity.DefaultIdentityName)
         
         # server
         serverName = "chat.freenode.net"
         server = IrcServer(serverName)
-        server.setPort(8001)
+        if ssl:
+            server.setPort(IrcServer.DefaultSslPort)
+            server.setUseSSL(True)
+        else:
+            server.setPort(IrcServer.DefaultPort)
         network.setServer(server)
         
         # channel
@@ -766,6 +775,8 @@
         if not identityOnly:
             network = IrcNetwork.createDefaultNetwork()
             self.__networks[network.getName()] = network
+            network = IrcNetwork.createDefaultNetwork(True)
+            self.__networks[network.getName()] = network
         
         self.dataChanged.emit()
     
--- a/Network/IRC/IrcServerEditDialog.py	Sun Dec 02 13:48:01 2012 +0100
+++ b/Network/IRC/IrcServerEditDialog.py	Sun Dec 02 15:35:18 2012 +0100
@@ -31,10 +31,11 @@
         
         self.__okButton = self.buttonBox.button(QDialogButtonBox.Ok)
         
-        self.serverEdit.setText(server.getName())
-        self.portSpinBox.setValue(server.getPort())
-        self.passwordEdit.setText(server.getPassword())
-        self.sslCheckBox.setChecked(server.useSSL())
+        if server:
+            self.serverEdit.setText(server.getName())
+            self.portSpinBox.setValue(server.getPort())
+            self.passwordEdit.setText(server.getPassword())
+            self.sslCheckBox.setChecked(server.useSSL())
         
         self.__updateOkButton()
     
--- a/Network/IRC/IrcWidget.py	Sun Dec 02 13:48:01 2012 +0100
+++ b/Network/IRC/IrcWidget.py	Sun Dec 02 15:35:18 2012 +0100
@@ -13,6 +13,11 @@
 from PyQt4.QtCore import pyqtSlot, Qt, QByteArray, QTimer
 from PyQt4.QtGui import QWidget, QToolButton, QLabel
 from PyQt4.QtNetwork import QTcpSocket, QAbstractSocket
+try:
+    from PyQt4.QtNetwork import QSslSocket, QSslError   # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
+    SSL_AVAILABLE = True
+except ImportError:
+    SSL_AVAILABLE = False
 
 from E5Gui import E5MessageBox
 
@@ -30,6 +35,10 @@
     """
     Class implementing the IRC window.
     """
+    ServerDisconnected = 1
+    ServerConnected = 2
+    ServerConnecting = 3
+    
     def __init__(self, parent=None):
         """
         Constructor
@@ -66,16 +75,13 @@
         self.__server = None
         self.__registering = False
         
+        self.__connectionState = IrcWidget.ServerDisconnected
+        self.__sslErrorLock = False
+        
         self.__buffer = ""
         self.__userPrefix = {}
         
-        # create TCP socket
-        self.__socket = QTcpSocket(self)
-        self.__socket.hostFound.connect(self.__hostFound)
-        self.__socket.connected.connect(self.__hostConnected)
-        self.__socket.disconnected.connect(self.__hostDisconnected)
-        self.__socket.readyRead.connect(self.__readyRead)
-        self.__socket.error.connect(self.__tcpError)
+        self.__socket = None
         
         self.__patterns = [
             # :foo.bar.net COMMAND some message
@@ -143,11 +149,43 @@
                 self.__userName = identity.getIdent()
                 self.__quitMessage = identity.getQuitMessage()
                 if self.__server:
-                    self.networkWidget.addServerMessage(self.trUtf8("Info"),
-                        self.trUtf8("Looking for server {0} (port {1})...").format(
-                            self.__server.getName(), self.__server.getPort()))
-                    self.__socket.connectToHost(self.__server.getName(),
-                                                self.__server.getPort())
+                    useSSL = self.__server.useSSL()
+                    if useSSL and not SSL_AVAILABLE:
+                        E5MessageBox.critical(self,
+                            self.trUtf8("SSL Connection"),
+                            self.trUtf8("""An encrypted connection to the IRC network"""
+                                        """ was requested but SSL is not available."""
+                                        """ Please change the server configuration."""))
+                        return
+                    
+                    if useSSL:
+                        # create SSL socket
+                        self.__socket = QSslSocket(self)
+                        self.__socket.encrypted.connect(self.__hostConnected)
+                        self.__socket.sslErrors.connect(self.__sslErrors)
+                    else:
+                        # create TCP socket
+                        self.__socket = QTcpSocket(self)
+                        self.__socket.connected.connect(self.__hostConnected)
+                    self.__socket.hostFound.connect(self.__hostFound)
+                    self.__socket.disconnected.connect(self.__hostDisconnected)
+                    self.__socket.readyRead.connect(self.__readyRead)
+                    self.__socket.error.connect(self.__tcpError)
+                    
+                    self.__connectionState = IrcWidget.ServerConnecting
+                    if useSSL:
+                        self.networkWidget.addServerMessage(self.trUtf8("Info"),
+                            self.trUtf8("Looking for server {0} (port {1}) using"
+                                        " an SSL encrypted connection...").format(
+                                self.__server.getName(), self.__server.getPort()))
+                        self.__socket.connectToHostEncrypted(self.__server.getName(),
+                                                             self.__server.getPort())
+                    else:
+                        self.networkWidget.addServerMessage(self.trUtf8("Info"),
+                            self.trUtf8("Looking for server {0} (port {1})...").format(
+                                self.__server.getName(), self.__server.getPort()))
+                        self.__socket.connectToHost(self.__server.getName(),
+                                                    self.__server.getPort())
         else:
             ok = E5MessageBox.yesNo(self,
                 self.trUtf8("Disconnect from Server"),
@@ -164,7 +202,7 @@
                     channel.deleteLater()
                     channel = None
                 self.__send("QUIT :" + self.__quitMessage)
-                self.__socket.close()
+                self.__socket and self.__socket.close()
                 self.__userName = ""
                 self.__identityName = ""
                 self.__quitMessage = ""
@@ -267,7 +305,8 @@
         
         @param data data to be sent (string)
         """
-        self.__socket.write(QByteArray("{0}\r\n".format(data).encode("utf-8")))
+        if self.__socket:
+            self.__socket.write(QByteArray("{0}\r\n".format(data).encode("utf-8")))
     
     def __hostFound(self):
         """
@@ -317,6 +356,12 @@
         self.__nickName = ""
         self.__nickIndex = -1
         self.__channelTypePrefixes = ""
+        
+        self.__socket.deleteLater()
+        self.__socket = None
+        
+        self.__connectionState = IrcWidget.ServerDisconnected
+        self.__sslErrorLock = False
     
     def __readyRead(self):
         """
@@ -498,6 +543,7 @@
         
         if code == 1:
             # register with services after the welcome message
+            self.__connectionState = IrcWidget.ServerConnected
             self.__registerWithServices()
             QTimer.singleShot(1000, self.__autoJoinChannels)
         elif code == 5:
@@ -543,7 +589,12 @@
         """
         if error == QAbstractSocket.RemoteHostClosedError:
             # ignore this one, it's a disconnect
-            pass
+            if self.__sslErrorLock:
+                self.networkWidget.addErrorMessage(self.trUtf8("SSL Error"),
+                    self.trUtf8("""Connection to server {0} (port {1}) lost while"""
+                                """ waiting for user response to an SSL error.""").format(
+                    self.__server.getName(), self.__server.getPort()))
+                self.__connectionState = IrcWidget.ServerDisconnected
         elif error == QAbstractSocket.HostNotFoundError:
             self.networkWidget.addErrorMessage(self.trUtf8("Socket Error"),
                 self.trUtf8("The host was not found. Please check the host name"
@@ -557,6 +608,45 @@
                 self.trUtf8("The following network error occurred:<br/>{0}").format(
                 self.__socket.errorString()))
     
+    def __sslErrors(self, errors):
+        """
+        Private slot to handle SSL errors.
+        
+        @param errors list of SSL errors (list of QSslError)
+        """
+        errorString = ""
+        if errors:
+            self.__sslErrorLock = True
+            errorStrings = []
+            for err in errors:
+                errorStrings.append(err.errorString())
+            errorString = '.<br/>'.join(errorStrings)
+            ret = E5MessageBox.yesNo(self,
+                self.trUtf8("SSL Errors"),
+                self.trUtf8("""<p>SSL Errors:</p>"""
+                            """<p>{0}</p>"""
+                            """<p>Do you want to ignore these errors?</p>""")\
+                    .format(errorString),
+                icon=E5MessageBox.Warning)
+            self.__sslErrorLock = False
+        else:
+            ret = True
+        if ret:
+            self.networkWidget.addErrorMessage(self.trUtf8("SSL Error"),
+                self.trUtf8("""The SSL certificate for the server {0} (port {1})"""
+                            """ failed the authenticity check.""").format(
+                self.__server.getName(), self.__server.getPort()))
+            if self.__connectionState == IrcWidget.ServerConnecting:
+                self.__socket.ignoreSslErrors()
+        else:
+            self.networkWidget.addErrorMessage(self.trUtf8("SSL Error"),
+                self.trUtf8("""Could not connect to {0} (port {1}) using an SSL"""
+                            """ encrypted connection. Either the server does not"""
+                            """ support SSL (did you use the correct port?) or"""
+                            """ you rejected the certificate.<br/>{2}""").format(
+                self.__server.getName(), self.__server.getPort(), errorString))
+            self.__socket.close()
+    
     def __setUserPrivilegePrefix(self, prefix1, prefix2):
         """
         Private method to set the user privilege prefix.

eric ide

mercurial