def execCommand(self, callable, *args, **keywds): try: result = zanshin.util.blockUntil(callable, *args, **keywds) return (1, result) except Utility.CertificateVerificationError, err: assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS try: result = (2, None) # Send the message to destroy the progress dialog first. This needs # to be done in this order on Linux because otherwise killing # the progress dialog will also kill the SSL error dialog. # Weird, huh? Welcome to the world of wx... callMethodInUIThread(self.callback, result) if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate( err.host, err.untrustedCertificates[0], self.reconnect) else: d = ssl.askIgnoreSSLError(err.host, err.untrustedCertificates[0], err.args[0], self.reconnect) waitForDeferred(d) return result except Exception, e: # There is a bug in the M2Crypto code which needs # to be fixed. #log.exception('This should not happen') return (0, (CANT_CONNECT, _(u"SSL error.")))
def execCommand(self, callable, *args, **keywds): try: result = zanshin.util.blockUntil(callable, *args, **keywds) return (1, result) except Utility.CertificateVerificationError, err: assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS try: result = (2, None) # Send the message to destroy the progress dialog first. This needs # to be done in this order on Linux because otherwise killing # the progress dialog will also kill the SSL error dialog. # Weird, huh? Welcome to the world of wx... callMethodInUIThread(self.callback, result) if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate(err.host, err.untrustedCertificates[0], self.reconnect) else: d = ssl.askIgnoreSSLError(err.host, err.untrustedCertificates[0], err.args[0], self.reconnect) waitForDeferred(d) return result except Exception, e: # There is a bug in the M2Crypto code which needs # to be fixed. #log.exception('This should not happen') return (0, (CANT_CONNECT, _(u"SSL error.")))
def blockUntil(self, callable, *args, **keywds): # Since there can be several errors in a connection, we must keep # trying until we either get a successful connection or the user # decides to cancel/disconnect, or there is an error we don't know # how to deal with. while True: try: return zanshin.util.blockUntil(callable, *args, **keywds) except Utility.CertificateVerificationError, err: self._reconnect = False assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS retry = lambda: True if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate( err.host, err.untrustedCertificates[0], retry) else: d = ssl.askIgnoreSSLError( err.host, err.untrustedCertificates[0], err.args[0], retry) if not waitForDeferred(d): raise ActivityAborted(_(u"Cancelled by user")) except M2Crypto.SSL.Checker.WrongHost, err: self._reconnect = False retry = lambda: True d = ssl.askIgnoreSSLError( err.expectedHost, err.pem, messages.SSL_HOST_MISMATCH % {'actualHost': err.actualHost}, retry) if not waitForDeferred(d): raise ActivityAborted(_(u"Cancelled by user"))
def blockUntil(self, callable, *args, **keywds): # Since there can be several errors in a connection, we must keep # trying until we either get a successful connection or the user # decides to cancel/disconnect, or there is an error we don't know # how to deal with. while True: try: return zanshin.util.blockUntil(callable, *args, **keywds) except Utility.CertificateVerificationError, err: self._reconnect = False assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS retry = lambda: True if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate( err.host, err.untrustedCertificates[0], retry) else: d = ssl.askIgnoreSSLError(err.host, err.untrustedCertificates[0], err.args[0], retry) if not waitForDeferred(d): raise ActivityAborted(_(u"Cancelled by user")) except M2Crypto.SSL.Checker.WrongHost, err: self._reconnect = False retry = lambda: True d = ssl.askIgnoreSSLError( err.expectedHost, err.pem, messages.SSL_HOST_MISMATCH % {'actualHost': err.actualHost}, retry) if not waitForDeferred(d): raise ActivityAborted(_(u"Cancelled by user"))
def displayedRecoverableSSLErrorDialog(self, err, mailMessage=None, dryRun=False): if __debug__: trace("displayedRecoverableSSLErrorDialog") # The isOnline check is performed in the Twisted thread # so pass in a view. if not dryRun and (self.shuttingDown or not Globals.mailService.isOnline(self.view) or \ self.cancel): return self._resetClient() if isinstance(err, Utility.CertificateVerificationError): assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS # Post an asynchronous event to the main thread where # we ask the user if they would like to trust this # certificate. The main thread will then initiate a retry # when the new certificate has been added. try: if not dryRun: if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate( err.host, err.untrustedCertificates[0], self.reconnect) else: d = ssl.askIgnoreSSLError(err.host, err.untrustedCertificates[0], err.args[0], self.reconnect) d.addCallback(lambda dummy: True) return True except Exception, e: # This code should never be reached. log.exception( 'Error raised in SSL Layer which requires investigation.') return False
def displayedRecoverableSSLErrorDialog(self, err, mailMessage=None, dryRun=False): if __debug__: trace("displayedRecoverableSSLErrorDialog") # The isOnline check is performed in the Twisted thread # so pass in a view. if not dryRun and (self.shuttingDown or not Globals.mailService.isOnline(self.view) or \ self.cancel): return self._resetClient() if isinstance(err, Utility.CertificateVerificationError): assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS # Post an asynchronous event to the main thread where # we ask the user if they would like to trust this # certificate. The main thread will then initiate a retry # when the new certificate has been added. try: if not dryRun: if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate(err.host, err.untrustedCertificates[0], self.reconnect) else: d = ssl.askIgnoreSSLError(err.host, err.untrustedCertificates[0], err.args[0], self.reconnect) d.addCallback(lambda dummy: True) return True except Exception, e: # This code should never be reached. log.exception('Error raised in SSL Layer which requires investigation.') return False
err.args[0], self.reconnect) d.addCallback(lambda dummy: True) return True except Exception, e: # This code should never be reached. log.exception( 'Error raised in SSL Layer which requires investigation.') return False elif isinstance(err, WrongHost): if not dryRun: d = ssl.askIgnoreSSLError(err.expectedHost, err.pem, messages.SSL_HOST_MISMATCH % \ {'actualHost': err.actualHost}, self.reconnect) d.addCallback(lambda dummy: True) return True return False def cancelLastRequest(self): if __debug__: trace("cancelLastRequest") # This feature is still experimental
if isinstance(err, WrongHost): # Post an asynchronous event to the main thread where # we ask the user if they would like to continue even though # the certificate identifies a different host. if self.callback: # Send the message to destroy the progress dialog first. This needs # to be done in this order on Linux because otherwise killing # the progress dialog will also kill the SSL error dialog. # Weird, huh? Welcome to the world of wx... callMethodInUIThread(self.callback, (2, None)) d = ssl.askIgnoreSSLError(err.expectedHost, err.pem, messages.SSL_HOST_MISMATCH % \ {'actualHost': err.actualHost}, self.reconnect) d.addCallback(lambda dummy: True) return self._actionCompleted() errorText = unicode(err.__str__(), 'utf8', 'ignore') if self.callback: callMethodInUIThread(self.callback, (0, errorText)) else: if isinstance(err, errors.IMAPTimeoutException): alertMailError(constants.MAIL_PROTOCOL_TIMEOUT_ERROR, self.account, {'hostName': self.account.host},
class AbstractDownloadClient(object): """ Base class for Chandler download transports (IMAP, POP, etc.) Encapsulates logic for interactions between Twisted protocols (POP, IMAP) and Chandler protocol clients""" #Subclasses overide these constants accountType = AccountBase clientType = "AbstractDownloadClient" factoryType = AbstractDownloadClientFactory defaultPort = 0 def __init__(self, view, account, mailWorker): """ @param view: An Instance of C{RepositoryView} @type view: C{RepositoryView} @param account: An Instance of C{DownloadAccountBase} @type account: C{DownloadAccount} @param mailWorker: An Instance of C{MailWorker} @type mailWorker: C{MailWorker} @return: C{None} """ assert isinstance(account, self.accountType) assert isinstance(view, RepositoryView) assert isinstance(mailWorker, mailworker.MailWorker) self.view = view self.mailWorker = mailWorker #These values exist for life of client self.accountUUID = account.itsUUID self.account = None self.shuttingDown = False # Stores the total number of messages # downloaded during the life of the # client. In the IMAP example this # value would hold the total number # of messages downloaded for all # folders where as DownloadVars.totalDownloaded # would hold the total number of messages download # for the given folder. self.totalDownloaded = 0 # These values are reassigned on each # connection to the server self.performingAction = False self.waitingOnCommit = False self.waitCallback = None self.testing = False self.logIn = True self.callback = None self.statusMessages = False self.factory = None self.proto = None self.reconnect = None self.errorHandled = False # Used to Log client server protocol exchanges. # On error if constants.DEBUG_CLIENT_SERVER = 1 # the catchErrors method will print the values # in the buffer to the stdout. self.clientServerBuffer = None # Cancel is experimental and used # with the account testing dialog. # More work still needs to be done # for it to be useful for all protocol # based operations. self.cancel = False #The internal class method to #call after initial connection #to the server and login / SSL / TLS # is completed. self.cb = None self.vars = None def getMail(self): """Retrieves mail from a download protocol (POP, IMAP)""" if __debug__: trace("getMail") # Tells whether to print status messages self.statusMessages = True # Tell what method to call if the SSL acceptance # dialog needs to be displayed self.reconnect = self.getMail # The first callback after login / TLS /SSL # complete self.cb = self._getMail # Move code execution path from current thread # to Reactor Asynch thread reactor.callFromThread(self._connectToServer) def testAccountSettings(self, callback, reconnect, logIn=True): """Tests the account settings for a download protocol (POP, IMAP). Raises an error if unable to establish or communicate properly with the a server. """ if __debug__: trace("testAccountSettings") # The isOnline check is performed on the Main Thread # so no need to pass in a view. if not Globals.mailService.isOnline(): return assert (callback is not None) assert (reconnect is not None) assert (isinstance(logIn, bool)) # Tells whether to print status messages self.statusMessages = False # Tell what method to call on reconnect # when a SSL dialog is displayed. # When the dialog is shown the # protocol code terminates the # connection and calls reconnect # if the cert has been accepted self.reconnect = reconnect # The method to call in the UI Thread # when the testing is complete. # This method is called for both success and failure. self.callback = callback # Preview timeframe addition that # specifies whether the protocol should # login the user as part of the account # testing. A value of True will log # the user in as part of the test. self.logIn = logIn # The first callback after login / TLS /SSL # complete self.cb = self._accountTestingComplete # Flag indicating we are in test mode self.testing = True # Move code execution path from current thread # to Reactor Asynch thread reactor.callFromThread(self._connectToServer) def _connectToServer(self): if __debug__: trace("_connectToServer") if self.cancel: return self._resetClient() self.view.refresh() #Overidden method self._getAccount() # The isOnline check is performed in the Twisted thread # so pass in a view. if not Globals.mailService.isOnline(self.view): if self.statusMessages: msg = constants.MAIL_PROTOCOL_OFFLINE % \ {"accountName": self.account.displayName} setStatusMessage(msg) return if self.performingAction: if __debug__: trace("%s is currently in use request ignored" % self.clientType) return self.performingAction = True if self.statusMessages: msg = constants.MAIL_PROTOCOL_CONNECTION \ % {"accountName": self.account.displayName, "serverDNSName": self.account.host} setStatusMessage(msg) self.factory = self.factoryType(self) if self.testing: # If in testing mode then do not want to retry connection or # wait a long period for a timeout self.factory.retries = 0 self.factory.timeout = constants.TESTING_TIMEOUT if self.account.connectionSecurity == 'SSL': ssl.connectSSL(self.account.host, self.account.port, self.factory, self.view) else: ssl.connectTCP(self.account.host, self.account.port, self.factory, self.view) def calculateCommitNumber(self): # Low commit numbers are used # till the UI has enough messages to # extend pass the scroll bars. # At that point the commit sizes # are increased gradually until # reaching the ideal number of # commits vs. downloaded messages # from a performance standpoint. total = self.totalDownloaded if total < 24: return 6 elif total >= 24 and total < 100: return 20 elif total >= 100 and total < 300: return 100 elif total >= 300 and total < 600: return 200 return constants.MAX_COMMIT def catchErrors(self, err): """ This method captures all errors thrown while in the Twisted Reactor Thread as well as errors raised by non-Twisted code while in the Twisted Reactor Thread. catchErrors will print a stacktrace of C{failure.Failure} objects to the chandler.log. catchErrors also handles c{Exception}s but will not log the stacktrace to the chandler.log since this method is out of the scope of the original c{Exception}. The caller must log its c{Exception} via the logging.exception method. @param err: The error thrown @type err: C{failure.Failure} or c{Exception} @return: C{None} """ if __debug__: trace("catchErrors") self.waitingOnCommit = False if self.waitCallback is not None: try: self.waitCallback.cancel() except error.AlreadyCalled: pass self.waitCallback = None if __debug__: if constants.DEBUG_CLIENT_SERVER == 1 and \ self.clientServerBuffer: # Prints the last four client server exchanges to # the stdout for debugging. print self.clientServerBuffer if constants.DEBUG_CLIENT_SERVER == 2: try: raise err except Exception, err: #Capture the error to the logger logging.exception(err) if self.vars: # If self.vars is not None then the error # was raised during an Incoming Mail download. # In this case we want to notify the Mail Worker # so it can perform any cleanup that is needed. self.mailWorker.queueRequest( (mailworker.ERROR_REQUEST, self, self.accountUUID)) # Flag that tells the connection factory # that the error has been handled self.errorHandled = True # In this case don't try to clean up the transport connection # but do reset the client variables # The isOnline check is performed in the Twisted thread so # pass in a view. if self.shuttingDown or not Globals.mailService.isOnline(self.view) or \ self.factory is None: self._resetClient() return # If we cancelled the request then gracefully disconnect from # the server and reset the client variables but do not display # the error. if self.cancel: return self._actionCompleted() if isinstance(err, failure.Failure): if err.check(error.ConnectionDone): if self.factory.retries < self.factory.sendFinished <= 0: #The error processing for lost connections is in the Factory #class so return here and let the Factory handle the reconnection logic. return #set the value of the error to something more meaningful than #'Connection closed cleanly.' err.value = self.factory.exception( constants.MAIL_PROTOCOL_CONNECTION_ERROR) err = err.value if self.statusMessages: # Reset the status bar since the error will be displayed # in a dialog or handled by a callback method. setStatusMessage(u"") if isinstance(err, Utility.CertificateVerificationError): assert err.args[1] == 'certificate verify failed' # Reason why verification failed is stored in err.args[0], see # codes at http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS # Post an asynchronous event to the main thread where # we ask the user if they would like to trust this # certificate. The main thread will then initiate a retry # when the new certificate has been added. try: if self.callback: # Send the message to destroy the progress dialog first. This needs # to be done in this order on Linux because otherwise killing # the progress dialog will also kill the SSL error dialog. # Weird, huh? Welcome to the world of wx... callMethodInUIThread(self.callback, (2, None)) if err.args[0] in ssl.unknown_issuer: d = ssl.askTrustServerCertificate( err.host, err.untrustedCertificates[0], self.reconnect) else: d = ssl.askIgnoreSSLError(err.host, err.untrustedCertificates[0], err.args[0], self.reconnect) d.addCallback(lambda dummy: True) except Exception, e: # This code should never be reached, and if it were, the _ would raise! log.exception( 'Error raised in SSL Layer which requires investigation.') callMethodInUIThread(self.callback, (0, _(u"SSL error."))) return self._actionCompleted()