summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mail/changes/feature_3464-make-smtprelay-emit-signals1
-rw-r--r--mail/changes/feature_3480_add_imap_events1
-rw-r--r--mail/src/leap/mail/imap/fetch.py62
-rw-r--r--mail/src/leap/mail/imap/service/imap.py34
-rw-r--r--mail/src/leap/mail/smtp/__init__.py7
-rw-r--r--mail/src/leap/mail/smtp/smtprelay.py42
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)