diff options
| author | Ruben Pollan <meskio@sindominio.net> | 2015-11-18 00:27:56 +0100 | 
|---|---|---|
| committer | Ruben Pollan <meskio@sindominio.net> | 2015-12-09 19:21:58 +0100 | 
| commit | 8fc1258ace65be2bb828bf302fc0661cdd128bd7 (patch) | |
| tree | 705ff34d8d077eb5a3d752468161fe345082211d | |
| parent | 3360cc70b7752f6ebedb56cb8ffc01b81cd640d6 (diff) | |
[feat] postfix lookup against couchdb for client smtp fingerprint
- Resolves: #4285
| -rw-r--r-- | changes/next-changelog.txt | 1 | ||||
| -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-- | src/leap/mx/couchdbhelper.py | 28 | ||||
| -rw-r--r-- | src/leap/mx/fingerprint_resolver.py | 98 | 
6 files changed, 151 insertions, 0 deletions
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 <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. 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/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  | 
