Sun, 02 Dec 2012 15:35:18 +0100
Implemented SSL support for IRC.
--- 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.