diff options
| author | Kali Kaneko <kali@leap.se> | 2013-08-20 17:44:26 +0200 | 
|---|---|---|
| committer | Kali Kaneko <kali@leap.se> | 2013-08-20 17:44:26 +0200 | 
| commit | 908bf38496f062dbd2da4884cdce0bcfc5e85101 (patch) | |
| tree | 626664a9ca4965d050dbb1d2dfcd7b59b1d4979b | |
| parent | da2f5b76df7e14ff1efe38e43e976a2f0a365781 (diff) | |
| parent | 8bff5f1a55f845a77effc23e07de51df4480605f (diff) | |
Merge branch 'develop' of ssh://leap.se/leap_mail into develop
| -rw-r--r-- | mail/changes/feature_3464-make-smtprelay-emit-signals | 1 | ||||
| -rw-r--r-- | mail/changes/feature_3480_add_imap_events | 1 | ||||
| -rw-r--r-- | mail/src/leap/mail/imap/fetch.py | 62 | ||||
| -rw-r--r-- | mail/src/leap/mail/imap/service/imap.py | 34 | ||||
| -rw-r--r-- | mail/src/leap/mail/smtp/__init__.py | 7 | ||||
| -rw-r--r-- | mail/src/leap/mail/smtp/smtprelay.py | 42 | 
6 files changed, 110 insertions, 37 deletions
| diff --git a/mail/changes/feature_3464-make-smtprelay-emit-signals b/mail/changes/feature_3464-make-smtprelay-emit-signals new file mode 100644 index 0000000..987b0e3 --- /dev/null +++ b/mail/changes/feature_3464-make-smtprelay-emit-signals @@ -0,0 +1 @@ +  o Emit signals to notify UI for SMTP relay events. Closes #3464. diff --git a/mail/changes/feature_3480_add_imap_events b/mail/changes/feature_3480_add_imap_events new file mode 100644 index 0000000..fc503e8 --- /dev/null +++ b/mail/changes/feature_3480_add_imap_events @@ -0,0 +1 @@ +  o Add events for notifications about imap activity. Closes: #3480 diff --git a/mail/src/leap/mail/imap/fetch.py b/mail/src/leap/mail/imap/fetch.py index 4a939fd..4eb90b4 100644 --- a/mail/src/leap/mail/imap/fetch.py +++ b/mail/src/leap/mail/imap/fetch.py @@ -1,15 +1,43 @@ +# -*- coding: utf-8 -*- +# fetch.py +# Copyright (C) 2013 LEAP +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program.  If not, see <http://www.gnu.org/licenses/>. +""" +Incoming mail fetcher. +"""  import logging  import json  import ssl +import time  from twisted.python import log  from twisted.internet import defer  from twisted.internet.task import LoopingCall  from twisted.internet.threads import deferToThread +from leap.common import events as leap_events  from leap.common.check import leap_assert, leap_assert_type  from leap.soledad import Soledad +from leap.common.events.events_pb2 import IMAP_FETCHED_INCOMING +from leap.common.events.events_pb2 import IMAP_MSG_PROCESSING +from leap.common.events.events_pb2 import IMAP_MSG_DECRYPTED +from leap.common.events.events_pb2 import IMAP_MSG_SAVED_LOCALLY +from leap.common.events.events_pb2 import IMAP_MSG_DELETED_INCOMING + +  logger = logging.getLogger(__name__) @@ -100,8 +128,12 @@ class LeapIncomingMail(object):          try:              self._soledad.sync() +            fetched_ts = time.mktime(time.gmtime())              doclist = self._soledad.get_from_index("just-mail", "*") -            log.msg("there are %s mails" % (len(doclist),)) +            num_mails = len(doclist) +            log.msg("there are %s mails" % (num_mails,)) +            leap_events.signal( +                IMAP_FETCHED_INCOMING, str(num_mails), str(fetched_ts))              return doclist          except ssl.SSLError as exc:              logger.warning('SSL Error while syncing soledad: %r' % (exc,)) @@ -117,8 +149,12 @@ class LeapIncomingMail(object):          if not doclist:              logger.debug("no docs found")              return -        for doc in doclist: -            logger.debug("processing doc: %s" % doc) +        num_mails = len(doclist) +        for index, doc in enumerate(doclist): +            logger.debug("processing doc %d of %d: %s" % ( +                index, num_mails, doc)) +            leap_events.signal( +                IMAP_MSG_PROCESSING, str(index), str(num_mails))              keys = doc.content.keys()              if self.ENC_SCHEME_KEY in keys and self.ENC_JSON_KEY in keys: @@ -132,13 +168,17 @@ class LeapIncomingMail(object):      def _decrypt_msg(self, doc, encdata):          log.msg('decrypting msg')          key = self._pkey -        if len(encdata) == 0: -            return -        decrdata = (self._keymanager.decrypt( -            encdata, key, -            # XXX get from public method instead -            passphrase=self._soledad._passphrase)) - +        try: +            decrdata = (self._keymanager.decrypt( +                encdata, key, +                # XXX get from public method instead +                passphrase=self._soledad._passphrase)) +            ok = True +        except Exception as exc: +            logger.warning("Error while decrypting msg: %r" % (exc,)) +            decrdata = "" +            ok = False +        leap_events.signal(IMAP_MSG_DECRYPTED, ok)          # XXX TODO: defer this properly          return self._process_decrypted(doc, decrdata) @@ -184,8 +224,10 @@ class LeapIncomingMail(object):                  rawmsg = rawmsg.replace(pgp_message, decrdata)              # add to inbox and delete from soledad              self._inbox.addMessage(rawmsg, (self.RECENT_FLAG,)) +            leap_events.signal(IMAP_MSG_SAVED_LOCALLY)              doc_id = doc.doc_id              self._soledad.delete_doc(doc)              log.msg("deleted doc %s from incoming" % doc_id) +            leap_events.signal(IMAP_MSG_DELETED_INCOMING)          except Exception as e:              logger.error("Problem processing incoming mail: %r" % (e,)) diff --git a/mail/src/leap/mail/imap/service/imap.py b/mail/src/leap/mail/imap/service/imap.py index 1a8c15c..380324c 100644 --- a/mail/src/leap/mail/imap/service/imap.py +++ b/mail/src/leap/mail/imap/service/imap.py @@ -27,6 +27,7 @@ from twisted.internet.protocol import ServerFactory  from twisted.mail import imap4  from twisted.python import log +from leap.common import events as leap_events  from leap.common.check import leap_assert, leap_assert_type  from leap.keymanager import KeyManager  from leap.mail.imap.server import SoledadBackedAccount @@ -42,6 +43,10 @@ INCOMING_CHECK_PERIOD = 5  # The period between succesive checks of the incoming mail  # queue (in seconds) +from leap.common.events.events_pb2 import IMAP_SERVICE_STARTED +from leap.common.events.events_pb2 import IMAP_SERVICE_FAILED_TO_START +from leap.common.events.events_pb2 import IMAP_CLIENT_LOGIN +  class LeapIMAPServer(imap4.IMAP4Server):      """ @@ -84,6 +89,7 @@ class LeapIMAPServer(imap4.IMAP4Server):      def authenticateLogin(self, username, password):          # all is allowed so far. use realm instead +        leap_events.signal(IMAP_CLIENT_LOGIN, True)          return imap4.IAccount, self.theAccount, lambda: None @@ -150,15 +156,21 @@ def run_service(*args, **kwargs):      factory = LeapIMAPFactory(uuid, soledad)      from twisted.internet import reactor -    reactor.listenTCP(port, factory) - -    fetcher = LeapIncomingMail( -        keymanager, -        soledad, -        factory.theAccount, -        check_period) - -    fetcher.start_loop() -    logger.debug("IMAP4 Server is RUNNING in port  %s" % (port,)) -    return fetcher +    try: +        reactor.listenTCP(port, factory) +        fetcher = LeapIncomingMail( +            keymanager, +            soledad, +            factory.theAccount, +            check_period) +    except Exception as exc: +        # XXX cannot listen? +        logger.error("Error launching IMAP service: %r" % (exc,)) +        leap_events.signal(IMAP_SERVICE_FAILED_TO_START, str(port)) +        return +    else: +        fetcher.start_loop() +        logger.debug("IMAP4 Server is RUNNING in port  %s" % (port,)) +        leap_events.signal(IMAP_SERVICE_STARTED, str(port)) +        return fetcher diff --git a/mail/src/leap/mail/smtp/__init__.py b/mail/src/leap/mail/smtp/__init__.py index 3b4d9d6..1139afa 100644 --- a/mail/src/leap/mail/smtp/__init__.py +++ b/mail/src/leap/mail/smtp/__init__.py @@ -22,6 +22,7 @@ SMTP relay helper function.  from twisted.internet import reactor +from leap.common.events import proto, signal  from leap.mail.smtp.smtprelay import SMTPFactory @@ -70,4 +71,8 @@ def setup_smtp_relay(port, keymanager, smtp_host, smtp_port,      # configure the use of this service with twistd      factory = SMTPFactory(keymanager, config) -    reactor.listenTCP(port, factory) +    try: +        reactor.listenTCP(port, factory) +        signal(proto.SMTP_SERVICE_STARTED, str(smtp_port)) +    except CannotListenError: +        signal(proto.SMTP_SERVICE_FAILED_TO_START, str(smtp_port)) diff --git a/mail/src/leap/mail/smtp/smtprelay.py b/mail/src/leap/mail/smtp/smtprelay.py index 5f73be7..96eaa31 100644 --- a/mail/src/leap/mail/smtp/smtprelay.py +++ b/mail/src/leap/mail/smtp/smtprelay.py @@ -33,6 +33,7 @@ from email.parser import Parser  from leap.common.check import leap_assert, leap_assert_type +from leap.common.events import proto, signal  from leap.keymanager import KeyManager  from leap.keymanager.openpgp import OpenPGPKey  from leap.keymanager.errors import KeyNotFound @@ -67,8 +68,8 @@ def assert_config_structure(config):          {              HOST_KEY: '<str>',              PORT_KEY: <int>, -            USERNAME_KEY: '<str>', -            PASSWORD_KEY: '<str>', +            CERT_KEY: '<str>', +            KEY_KEY: '<str>',              ENCRYPTED_ONLY_KEY: <bool>,          } @@ -107,8 +108,8 @@ def validate_address(address):      @raise smtp.SMTPBadRcpt: Raised if C{address} is invalid.      """      leap_assert_type(address, str) -    # the following parses the address as described in RFC 2822 and -    # returns ('', '') if the parse fails. +    # in the following, the address is parsed as described in RFC 2822 and +    # ('', '') is returned if the parse fails.      _, address = parseaddr(address)      if address == '':          raise smtp.SMTPBadRcpt(address) @@ -186,8 +187,8 @@ class SMTPDelivery(object):                  {                      HOST_KEY: '<str>',                      PORT_KEY: <int>, -                    USERNAME_KEY: '<str>', -                    PASSWORD_KEY: '<str>', +                    CERT_KEY: '<str>', +                    KEY_KEY: '<str>',                      ENCRYPTED_ONLY_KEY: <bool>,                  }          @type config: dict @@ -250,13 +251,16 @@ class SMTPDelivery(object):          try:              address = validate_address(user.dest.addrstr)              pubkey = self._km.get_key(address, OpenPGPKey) -            log.msg("Accepting mail for %s..." % user.dest) +            log.msg("Accepting mail for %s..." % user.dest.addrstr) +            signal(proto.SMTP_RECIPIENT_ACCEPTED_ENCRYPTED, user.dest.addrstr)          except KeyNotFound:              # if key was not found, check config to see if will send anyway.              if self._config[ENCRYPTED_ONLY_KEY]: +                signal(proto.SMTP_RECIPIENT_REJECTED, user.dest.addrstr)                  raise smtp.SMTPBadRcpt(user.dest.addrstr)              log.msg("Warning: will send an unencrypted message (because "                      "encrypted_only' is set to False).") +            signal(proto.SMTP_RECIPIENT_ACCEPTED_UNENCRYPTED, user.dest.addrstr)          return lambda: EncryptedMessage(              self._origin, user, self._km, self._config) @@ -321,8 +325,8 @@ class EncryptedMessage(object):                  {                      HOST_KEY: '<str>',                      PORT_KEY: <int>, -                    USERNAME_KEY: '<str>', -                    PASSWORD_KEY: '<str>', +                    CERT_KEY: '<str>', +                    KEY_KEY: '<str>',                      ENCRYPTED_ONLY_KEY: <bool>,                  }          @type config: dict @@ -376,6 +380,7 @@ class EncryptedMessage(object):          """          log.msg("Connection lost unexpectedly!")          log.err() +        signal(proto.SMTP_CONNECTION_LOST, self._user.dest.addrstr)          # unexpected loss of connection; don't save          self.lines = [] @@ -387,6 +392,7 @@ class EncryptedMessage(object):          @type r: anything          """          log.msg(r) +        signal(proto.SMTP_SEND_MESSAGE_SUCCESS, self._user.dest.addrstr)      def sendError(self, e):          """ @@ -397,6 +403,7 @@ class EncryptedMessage(object):          """          log.msg(e)          log.err() +        signal(proto.SMTP_SEND_MESSAGE_ERROR, self._user.dest.addrstr)      def sendMessage(self):          """ @@ -416,17 +423,16 @@ class EncryptedMessage(object):          d = defer.Deferred()          factory = smtp.ESMTPSenderFactory( -            "", -            "", +            "",  # username is blank because server does not use auth. +            "",  # password is blank because server does not use auth.              self._fromAddress.addrstr,              self._user.dest.addrstr,              StringIO(msg),              d,              contextFactory=CtxFactory(self._config[CERT_KEY],                                        self._config[KEY_KEY]), -            requireAuthentication=False, -        ) - +            requireAuthentication=False) +        signal(proto.SMTP_SEND_MESSAGE_START, self._user.dest.addrstr)          reactor.connectTCP(              self._config[HOST_KEY],              self._config[PORT_KEY], @@ -496,10 +502,16 @@ class EncryptedMessage(object):              # try to get the recipient pubkey              pubkey = self._km.get_key(to_address, OpenPGPKey)              log.msg("Will encrypt the message to %s." % pubkey.fingerprint) +            signal(proto.SMTP_START_ENCRYPT_AND_SIGN, +                   "%s,%s" % (self._fromAddress.addrstr, to_address))              self._encrypt_and_sign_payload_rec(self._message, pubkey, signkey) +            signal(proto.SMTP_END_ENCRYPT_AND_SIGN, +                   "%s,%s" % (self._fromAddress.addrstr, to_address))          except KeyNotFound:              # at this point we _can_ send unencrypted mail, because if the              # configuration said the opposite the address would have been              # rejected in SMTPDelivery.validateTo(). -            self._sign_payload_rec(self._message, signkey)              log.msg('Will send unencrypted message to %s.' % to_address) +            signal(proto.SMTP_START_SIGN, self._fromAddress.addrstr) +            self._sign_payload_rec(self._message, signkey) +            signal(proto.SMTP_END_SIGN, self._fromAddress.addrstr) | 
