diff options
-rw-r--r-- | changes/feature_3464-make-smtprelay-emit-signals | 1 | ||||
-rw-r--r-- | changes/feature_3480_add_imap_events | 1 | ||||
-rw-r--r-- | src/leap/mail/imap/fetch.py | 62 | ||||
-rw-r--r-- | src/leap/mail/imap/service/imap.py | 34 | ||||
-rw-r--r-- | src/leap/mail/smtp/__init__.py | 7 | ||||
-rw-r--r-- | src/leap/mail/smtp/smtprelay.py | 42 |
6 files changed, 110 insertions, 37 deletions
diff --git a/changes/feature_3464-make-smtprelay-emit-signals b/changes/feature_3464-make-smtprelay-emit-signals new file mode 100644 index 0000000..987b0e3 --- /dev/null +++ b/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/changes/feature_3480_add_imap_events b/changes/feature_3480_add_imap_events new file mode 100644 index 0000000..fc503e8 --- /dev/null +++ b/changes/feature_3480_add_imap_events @@ -0,0 +1 @@ + o Add events for notifications about imap activity. Closes: #3480 diff --git a/src/leap/mail/imap/fetch.py b/src/leap/mail/imap/fetch.py index 4a939fd..4eb90b4 100644 --- a/src/leap/mail/imap/fetch.py +++ b/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/src/leap/mail/imap/service/imap.py b/src/leap/mail/imap/service/imap.py index 1a8c15c..380324c 100644 --- a/src/leap/mail/imap/service/imap.py +++ b/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/src/leap/mail/smtp/__init__.py b/src/leap/mail/smtp/__init__.py index 3b4d9d6..1139afa 100644 --- a/src/leap/mail/smtp/__init__.py +++ b/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/src/leap/mail/smtp/smtprelay.py b/src/leap/mail/smtp/smtprelay.py index 5f73be7..96eaa31 100644 --- a/src/leap/mail/smtp/smtprelay.py +++ b/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) |