From adea30df46100e2c66c3691ccf6242d317abb4ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Touceda?= Date: Thu, 24 Oct 2013 13:42:42 -0300 Subject: Make the mail received callback more robust and add support for ml --- src/leap/mx/couchdbhelper.py | 30 ++++++++--- src/leap/mx/mail_receiver.py | 115 ++++++++++++++++++++++++++----------------- 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() -- cgit v1.2.3