From 8fc1258ace65be2bb828bf302fc0661cdd128bd7 Mon Sep 17 00:00:00 2001 From: Ruben Pollan Date: Wed, 18 Nov 2015 00:27:56 +0100 Subject: [feat] postfix lookup against couchdb for client smtp fingerprint - Resolves: #4285 --- changes/next-changelog.txt | 1 + doc/DESIGN.md | 13 +++++ pkg/mx.conf.sample | 3 ++ pkg/mx.tac | 8 +++ src/leap/mx/couchdbhelper.py | 28 +++++++++++ src/leap/mx/fingerprint_resolver.py | 98 +++++++++++++++++++++++++++++++++++++ 6 files changed, 151 insertions(+) create mode 100644 src/leap/mx/fingerprint_resolver.py diff --git a/changes/next-changelog.txt b/changes/next-changelog.txt index fbee095..641e26c 100644 --- a/changes/next-changelog.txt +++ b/changes/next-changelog.txt @@ -10,6 +10,7 @@ I've added a new category `Misc` so we can track doc/style/packaging stuff. Features ~~~~~~~~ +- `#4285 `_: Add postfix lookup against couchdb for client smtp fingerprint - `#5959 `_: Make alias resolver to return *uuid@deliver.local* - `#1234 `_: Description of the new feature corresponding with issue #1234. - New feature 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=
subject=Delivery failure \ No newline at end of file diff --git a/pkg/mx.tac b/pkg/mx.tac index 4ae08f2..42d40a8 100755 --- a/pkg/mx.tac +++ b/pkg/mx.tac @@ -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/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 . + +""" +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 -- cgit v1.2.3