diff options
author | Kali Kaneko <kali@leap.se> | 2015-12-10 11:50:33 -0400 |
---|---|---|
committer | Kali Kaneko <kali@leap.se> | 2015-12-10 11:50:33 -0400 |
commit | cc066454a84e68f3e0d30fce3f10e65e86f86ebc (patch) | |
tree | 86c09f6a8890cf19894e3214a1400c3c6aab1be5 | |
parent | 9085a1662f9a2f306a38662e49a3cfac2d69d1b7 (diff) | |
parent | c944cb877313fec9841c731b1ce4c4a16fb4d5f0 (diff) |
Merge branch 'develop' into debian/platform-0.8
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | changes/next-changelog.txt | 2 | ||||
-rw-r--r-- | doc/DESIGN.md | 13 | ||||
-rw-r--r-- | pkg/mx.conf.sample | 3 | ||||
-rwxr-xr-x | pkg/mx.tac | 8 | ||||
-rw-r--r-- | pkg/requirements-leap.pip | 2 | ||||
-rw-r--r-- | src/leap/mx/couchdbhelper.py | 28 | ||||
-rw-r--r-- | src/leap/mx/fingerprint_resolver.py | 98 | ||||
-rw-r--r-- | src/leap/mx/mail_receiver.py | 9 | ||||
-rw-r--r-- | src/leap/mx/tests/test_mail_receiver.py | 16 |
10 files changed, 172 insertions, 8 deletions
@@ -9,6 +9,7 @@ dist build eggs +.eggs parts bin var diff --git a/changes/next-changelog.txt b/changes/next-changelog.txt index fbee095..0e3da74 100644 --- a/changes/next-changelog.txt +++ b/changes/next-changelog.txt @@ -10,12 +10,14 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ +- `#4285 <https://leap.se/code/issues/4285>`_: Add postfix lookup against couchdb for client smtp fingerprint - `#5959 <https://leap.se/code/issues/5959>`_: Make alias resolver to return *uuid@deliver.local* - `#1234 <https://leap.se/code/issues/1234>`_: Description of the new feature corresponding with issue #1234. - New feature without related issue number. Bugfixes ~~~~~~~~ +- `#7253 <https://leap.se/code/issues/7253>`_: Use the original message for encryption. - `#1235 <https://leap.se/code/issues/1235>`_: Description for the fixed stuff corresponding with issue #1235. - Bugfix without related issue number. diff --git a/doc/DESIGN.md b/doc/DESIGN.md index e33c6ae..dbfbc99 100644 --- a/doc/DESIGN.md +++ b/doc/DESIGN.md @@ -145,6 +145,19 @@ virtual transport instead, we should append the domain (eg 123456@example.org). see http://www.postfix.org/ADDRESS_REWRITING_README.html#resolve +#### fingerprint_resolver + +postfix config: + +``` +virtual_alias_map tcp:localhost:2424 +``` + +postfix sends "get 12:34:56:78:90:ab:cd:ef:12:34:56:78:90:ab:cd:ef:12:34:56:78" +providing an smtp fingerprint and fingerprint_resolver returns "200 2016-01-19", +where 2016-01-19 is the expiration date of the given fingerprint. If the +fingerprint does not exists or is expired it will return "500 NOT FOUND SRY". + #### Return values The return codes and content of the tcp maps are: diff --git a/pkg/mx.conf.sample b/pkg/mx.conf.sample index c9ad0f8..a649b73 100644 --- a/pkg/mx.conf.sample +++ b/pkg/mx.conf.sample @@ -14,6 +14,9 @@ port=4242 [check recipient] port=2244 +[fingerprint map] +port=2424 + [bounce] from=<address for the From: of the bounce email without domain> subject=Delivery failure
\ No newline at end of file @@ -24,6 +24,7 @@ from leap.mx import couchdbhelper from leap.mx.mail_receiver import MailReceiver from leap.mx.alias_resolver import AliasResolverFactory from leap.mx.check_recipient_access import CheckRecipientAccessFactory +from leap.mx.fingerprint_resolver import FingerprintResolverFactory try: from twisted.application import service, internet @@ -57,6 +58,7 @@ except ConfigParser.NoSectionError: alias_port = config.getint("alias map", "port") check_recipient_port = config.getint("check recipient", "port") +fingerprint_port = config.getint("fingerprint map", "port") cdb = couchdbhelper.ConnectedCouchDB(server, port=port, @@ -79,6 +81,12 @@ check_recipient = internet.TCPServer( interface="localhost") check_recipient.setServiceParent(application) +# Fingerprint map +fingerprint_map = internet.TCPServer( + fingerprint_port, FingerprintResolverFactory(couchdb=cdb), + interface="localhost") +fingerprint_map.setServiceParent(application) + # Mail receiver directories = [] for section in config.sections(): diff --git a/pkg/requirements-leap.pip b/pkg/requirements-leap.pip index 482d1e2..23ebb69 100644 --- a/pkg/requirements-leap.pip +++ b/pkg/requirements-leap.pip @@ -1,3 +1,3 @@ leap.common>=0.3.5 -leap.soledad.common>=0.4.5 +leap.soledad.common>=0.8.0 leap.keymanager>=0.3.4 diff --git a/src/leap/mx/couchdbhelper.py b/src/leap/mx/couchdbhelper.py index 115ecbe..de133d5 100644 --- a/src/leap/mx/couchdbhelper.py +++ b/src/leap/mx/couchdbhelper.py @@ -138,6 +138,34 @@ class ConnectedCouchDB(client.CouchDB): d.addCallbacks(_get_pubkey_cbk, log.err) return d + def getCertExpiry(self, fingerprint): + """ + Query couch and return a deferred that will fire with the expiration + date for the cert with the given fingerprint. + + :param fingerprint: The cert fingerprint + :type fingerprint: str + + :return: A deferred that will fire with the cert expiration date as a + str. + :rtype: Deferred + """ + d = self.openView(docId="Identity", + viewId="cert_expiry_by_fingerprint/", + key=fingerprint, + reduce=False, + include_docs=True) + + def _get_cert_expiry_cbk(result): + try: + expiry = result["rows"][0]["value"] + except (KeyError, IndexError): + expiry = None + return expiry + + d.addCallback(_get_cert_expiry_cbk) + return d + def put_doc(self, uuid, doc): """ Update a document. diff --git a/src/leap/mx/fingerprint_resolver.py b/src/leap/mx/fingerprint_resolver.py new file mode 100644 index 0000000..0a0850d --- /dev/null +++ b/src/leap/mx/fingerprint_resolver.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python +# -*- encoding: utf-8 -*- +# fingerprint_resolver.py +# Copyright (C) 2015 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/>. + +""" +Classes for resolve expiration date of certs. + +Test this with postmap -v -q "fingerprint" tcp:localhost:2424 +""" + + +from datetime import datetime +from twisted.internet.protocol import ServerFactory +from twisted.protocols import postfix +from twisted.python import log + +from leap.mx.tcp_map import TCP_MAP_CODE_SUCCESS +from leap.mx.tcp_map import TCP_MAP_CODE_PERMANENT_FAILURE + + +class LEAPPostfixTCPMapFingerprintServer(postfix.PostfixTCPMapServer): + """ + A postfix tcp map fingerprint resolver server. + """ + + def _cbGot(self, res): + """ + Return a code and message depending on the result of the factory's + get(). + + :param res: The fingerprint and expiration date of the cert + :type res: (str, str) + """ + fingerprint, expiry = (None, None) + if res is not None: + fingerprint, expiry = res + + if expiry is None: + code = TCP_MAP_CODE_PERMANENT_FAILURE + msg = "NOT FOUND SRY" + elif expiry < datetime.utcnow().strftime("%Y-%m-%d"): + code = TCP_MAP_CODE_PERMANENT_FAILURE + msg = "EXPIRED CERT" + else: + # properly encode expiry, otherwise twisted complains when replying + if isinstance(expiry, unicode): + expiry = expiry.encode("utf8") + code = TCP_MAP_CODE_SUCCESS + msg = fingerprint + " " + expiry + + self.sendCode(code, postfix.quote(msg)) + + +class FingerprintResolverFactory(ServerFactory, object): + """ + A factory for postfix tcp map fingerprint resolver servers. + """ + + protocol = LEAPPostfixTCPMapFingerprintServer + + def __init__(self, couchdb): + """ + Initialize the factory. + + :param couchdb: A CouchDB client. + :type couchdb: leap.mx.couchdbhelper.ConnectedCouchDB + """ + self._cdb = couchdb + + def get(self, fingerprint): + """ + Look up the cert expiration date based on fingerprint. + + :param fingerprint: The cert fingerprint. + :type fingerprint: str + + :return: A deferred that will be fired with the expiration date. + :rtype: Deferred + """ + log.msg("look up: %s" % (fingerprint,)) + d = self._cdb.getCertExpiry(fingerprint.lower()) + d.addCallback(lambda expiry: (fingerprint, expiry)) + d.addErrback(log.err) + return d diff --git a/src/leap/mx/mail_receiver.py b/src/leap/mx/mail_receiver.py index ea13658..4d82849 100644 --- a/src/leap/mx/mail_receiver.py +++ b/src/leap/mx/mail_receiver.py @@ -166,7 +166,7 @@ class MailReceiver(Service): :param pubkey: public key for the owner of the message :type pubkey: str :param message: message contents - :type message: email.message.Message + :type message: str :return: doc to sync with Soledad or None, None if something went wrong. @@ -177,13 +177,10 @@ class MailReceiver(Service): "I know: %r" % (pubkey,)) return None - # find message's encoding - message_as_string = message.as_string() - doc = ServerDocument(doc_id=str(pyuuid.uuid4())) # store plain text if pubkey is not available - data = {'incoming': True, 'content': message_as_string} + data = {'incoming': True, 'content': message} if pubkey is None or len(pubkey) == 0: doc.content = { self.INCOMING_KEY: True, @@ -385,7 +382,7 @@ class MailReceiver(Service): defer.returnValue(None) log.msg("Encrypting message to %s's pubkey" % (uuid,)) - doc = yield self._encrypt_message(pubkey, msg) + doc = yield self._encrypt_message(pubkey, mail_data) yield self._export_message(uuid, doc) yield self._remove(filepath) diff --git a/src/leap/mx/tests/test_mail_receiver.py b/src/leap/mx/tests/test_mail_receiver.py index e72cb1a..33967ea 100644 --- a/src/leap/mx/tests/test_mail_receiver.py +++ b/src/leap/mx/tests/test_mail_receiver.py @@ -19,6 +19,7 @@ MailReceiver tests """ +import codecs import json import os import os.path @@ -97,14 +98,27 @@ class MailReceiverTestCase(unittest.TestCase): yield defer_called self.assertTrue(os.path.exists(path)) + @defer.inlineCallbacks + def test_misleading_encoding(self): + msg, path = self.addMail( + "ñáûä", headers={'Content-Transfer-Encoding': '7Bit'}) + uuid, doc = yield self.defer_put_doc + self.assertEqual(uuid, UUID) + decmsg = self.decryptDoc(doc) + self.assertEqual(unicode(msg, "utf-8"), decmsg) + self.assertFalse(os.path.exists(path)) + def addMail(self, body="", filename="foo", to=ADDRESS, - frm="someone@domain.org", subject="sent subject"): + frm="someone@domain.org", subject="sent subject", + headers={}): msg = Message() msg.add_header("To", to) msg.add_header( "Delivered-To", UUID + "@deliver.local") msg.add_header("From", frm) msg.add_header("Subject", subject) + for header, value in headers.iteritems(): + msg.add_header(header, value) msg.set_payload(body) path = os.path.join(self.directory, "new", filename) |