summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTomás Touceda <chiiph@leap.se>2013-10-24 13:42:42 -0300
committerTomás Touceda <chiiph@leap.se>2013-10-28 11:28:54 -0300
commitadea30df46100e2c66c3691ccf6242d317abb4ba (patch)
tree508161ac598db425f2fffa3fc1248d1822948a9f /src
parent56bd5becca970c02c19d1b4b9a26cbbf5cb62d17 (diff)
Make the mail received callback more robust and add support for ml
Diffstat (limited to 'src')
-rw-r--r--src/leap/mx/couchdbhelper.py30
-rw-r--r--src/leap/mx/mail_receiver.py115
2 files changed, 94 insertions, 51 deletions
diff --git a/src/leap/mx/couchdbhelper.py b/src/leap/mx/couchdbhelper.py
index b500f17..41604ba 100644
--- a/src/leap/mx/couchdbhelper.py
+++ b/src/leap/mx/couchdbhelper.py
@@ -29,7 +29,6 @@ except ImportError:
print "for instructions on getting required dependencies."
try:
- from twisted.internet import defer
from twisted.python import log
except ImportError:
print "This software requires Twisted. Please see the README file"
@@ -140,20 +139,39 @@ class ConnectedCouchDB(client.CouchDB):
return uuid
return None
- def getPubKey(self, address):
+ def getPubKey(self, uuid):
+ """
+ Returns a deferred that will return the pubkey for the uuid provided
+
+ :param uuid: uuid for the user to query
+ :type uuid: str
+
+ :rtype: Deferred
+ """
d = self.openView(docId="Identity",
viewId="pgp_key_by_email/",
- key=address,
+ user_id=uuid,
reduce=False,
include_docs=True)
- d.addCallbacks(partial(self._get_pgp_key, address), log.err)
+ d.addCallbacks(partial(self._get_pgp_key, uuid), log.err)
return d
- def _get_pgp_key(self, address, result):
+ def _get_pgp_key(self, uuid, result):
+ """
+ Callback used to filter the correct pubkey from the result of
+ the query to the couchdb
+
+ :param uuid: uuid for the user that was queried
+ :type uuid: str
+ :param result: result dictionary for the db query
+ :type result: dict
+
+ :rtype: str or None
+ """
for row in result["rows"]:
- if row["key"] == address:
+ if row["doc"]["user_id"] == uuid:
return row["value"]
return None
diff --git a/src/leap/mx/mail_receiver.py b/src/leap/mx/mail_receiver.py
index c7652fc..bae489f 100644
--- a/src/leap/mx/mail_receiver.py
+++ b/src/leap/mx/mail_receiver.py
@@ -36,7 +36,6 @@ from email import message_from_string
from twisted.application.service import Service
from twisted.internet import inotify
-from twisted.internet.defer import DeferredList
from twisted.python import filepath, log
from leap.soledad.common.document import SoledadDocument
@@ -73,6 +72,7 @@ class MailReceiver(Service):
self._mail_couch_url = mail_couch_url
self._users_cdb = users_cdb
self._directories = directories
+ self._domain = socket.gethostbyaddr(socket.gethostname())[0]
def startService(self):
"""
@@ -90,22 +90,7 @@ class MailReceiver(Service):
callbacks=[self._process_incoming_email],
recursive=recursive)
- def _gather_uuid_pubkey(self, results):
- if len(results) < 2:
- return None, None
-
- # DeferredList results are structured like this:
- # [ (succeeded, pubkey), (succeeded, uuid) ]
- # succeeded is a bool value that specifies if the
- # corresponding callback succeeded
- pubkey_res, uuid_res = results
-
- pubkey = pubkey_res[1] if pubkey_res[0] else None
- uuid = uuid_res[1] if uuid_res[0] else None
-
- return uuid, pubkey
-
- def _encrypt_message(self, uuid_pubkey, address, message):
+ def _encrypt_message(self, pubkey, uuid, address, message):
"""
Given a UUID, a public key, address and a message, it encrypts
the message to that public key.
@@ -120,10 +105,13 @@ class MailReceiver(Service):
:param message: message contents
:type message: str
- :return: uuid, doc to sync with Soledad
+ :return: uuid, doc to sync with Soledad or None, None if
+ something went wrong.
:rtype: tuple(str, SoledadDocument)
"""
- uuid, pubkey = uuid_pubkey
+ if uuid is None or pubkey is None or len(pubkey) == 0:
+ return None, None
+
log.msg("Encrypting message to %s's pubkey" % (uuid,))
log.msg("Pubkey: %s" % (pubkey,))
@@ -174,6 +162,9 @@ class MailReceiver(Service):
:rtype: bool
"""
uuid, doc = uuid_doc
+ if uuid is None or doc is None:
+ return False
+
log.msg("Exporting message for %s" % (uuid,))
if uuid is None:
@@ -202,9 +193,41 @@ class MailReceiver(Service):
log.msg("Removing %s" % (filepath.path,))
filepath.remove()
log.msg("Done removing")
- except:
+ except Exception:
log.err()
+ def _get_owner(self, mail):
+ """
+ Given an email, returns the owner (who's delivered to) and the
+ uuid of the owner. In case of a mailing list mail, the owner
+ will end up being the mailing list address, but the owner's
+ uuid will be correct as long as the user exists in the system.
+
+ :param mail: mail to analyze
+ :type mail: email.message.Message
+
+ :returns: a tuple of (owner, uuid)
+ :rtype: tuple(str or None, str or None)
+ """
+ owner = mail["To"]
+ uuid = None
+
+ delivereds = mail.get_all("Delivered-To")
+ for to in delivereds:
+ name, addr = email.utils.parseaddr(to)
+ parts = addr.split("@")
+ if len(parts) > 1 and parts[1] == self._domain:
+ uuid = parts[0]
+ break
+
+ if owner is None: # default to Delivered-To
+ owner = mail["Delivered-To"]
+ if owner is None:
+ log.err("Malformed mail, neither To: nor "
+ "Delivered-To: field")
+
+ return owner, uuid
+
def _process_incoming_email(self, otherself, filepath, mask):
"""
Callback that processes incoming email.
@@ -218,28 +241,30 @@ class MailReceiver(Service):
this callback
:type mask: int
"""
- if os.path.split(filepath.dirname())[-1] == "new":
- log.msg("Processing new mail at %s" % (filepath.path,))
- with filepath.open("r") as f:
- mail_data = f.read()
- mail = message_from_string(mail_data)
- owner = mail["To"]
- if owner is None: # default to Delivered-To
- owner = mail["Delivered-To"]
- if owner is None:
- log.err("Malformed mail, neither To: nor "
- "Delivered-To: field")
- log.msg("Mail owner: %s" % (owner,))
-
- owner = email.utils.parseaddr(owner)[1]
- log.msg("%s received a new mail" % (owner,))
- dpubk = self._users_cdb.getPubKey(owner)
- duuid = self._users_cdb.queryByAddress(owner)
- d = DeferredList([dpubk, duuid])
- d.addCallbacks(self._gather_uuid_pubkey, log.err)
- d.addCallbacks(self._encrypt_message, log.err,
- (owner, mail_data))
- d.addCallbacks(self._export_message, log.err)
- d.addCallbacks(self._conditional_remove, log.err,
- (filepath,))
- d.addErrback(log.err)
+ try:
+ if os.path.split(filepath.dirname())[-1] == "new":
+ log.msg("Processing new mail at %s" % (filepath.path,))
+ with filepath.open("r") as f:
+ mail_data = f.read()
+ mail = message_from_string(mail_data)
+ owner, uuid = self._get_owner(mail)
+ if owner is None:
+ # This shouldn't happen, may be a bug in
+ # postfix? But we don't want to drop mail just
+ # because of a bug. Skipping this one...
+ return
+ log.msg("Mail owner: %s %s" % (owner, uuid))
+
+ if uuid is None:
+ log.msg("BUG: There was no uuid!")
+ return
+
+ d = self._users_cdb.getPubKey(uuid)
+ d.addCallbacks(self._encrypt_message, log.err,
+ (uuid, owner, mail_data))
+ d.addCallbacks(self._export_message, log.err)
+ d.addCallbacks(self._conditional_remove, log.err,
+ (filepath,))
+ d.addErrback(log.err)
+ except Exception:
+ log.err()