From 5a45acd3486f4e7f830647953731353cda916d51 Mon Sep 17 00:00:00 2001 From: drebs Date: Tue, 24 Mar 2015 15:09:18 -0300 Subject: [feat] reject incoming mail if no pgp key found Implement a PGP key lookup in the postfix smtp recipient restriction and virtual alias mapping levels. If no PGP key is found, then the address is rejected with a temporary error. Closes: #6795 --- src/leap/mx/alias_resolver.py | 45 ++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 24 deletions(-) (limited to 'src/leap/mx/alias_resolver.py') diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 45a3ed2..4247b57 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -28,10 +28,11 @@ TODO: try: # TODO: we should probably use the system alias somehow - # from twisted.mail import alias + # from twisted.mail import alias from twisted.protocols import postfix from twisted.python import log from twisted.internet import defer + from twisted.internet.protocol import ServerFactory except ImportError: print "This software requires Twisted. Please see the README file" print "for instructions on getting required dependencies." @@ -39,19 +40,20 @@ except ImportError: class LEAPPostFixTCPMapserver(postfix.PostfixTCPMapServer): def _cbGot(self, value): - if value is None: + uuid, pubkey = value + if uuid is None: self.sendCode(500, postfix.quote("NOT FOUND SRY")) + elif pubkey is None: + self.sendCode(400, postfix.quote("4.7.13 USER ACCOUNT DISABLED")) else: self.sendCode(200, postfix.quote(value)) -class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): +class AliasResolverFactory(ServerFactory): protocol = LEAPPostFixTCPMapserver - def __init__(self, couchdb, *args, **kwargs): - postfix.PostfixTCPMapDeferringDictServerFactory.__init__( - self, *args, **kwargs) + def __init__(self, couchdb): self._cdb = couchdb def _to_str(self, result): @@ -64,14 +66,14 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): log.msg("Result not found") return result - def spit_result(self, result): - """ - Formats the return codes in a postfix friendly format. - """ - if result is None: - return None - else: - return defer.succeed(result) + def _getPubKey(self, uuid): + if uuid is None: + return defer.succeed([None, None]) + d = defer.gatherResults([ + self._to_str(uuid), + self._cdb.getPubKey(uuid), + ]) + return d def get(self, key): """ @@ -79,13 +81,8 @@ class AliasResolverFactory(postfix.PostfixTCPMapDeferringDictServerFactory): At some point we will have to consider the domain part too. """ - try: - log.msg("Query key: %s" % (key,)) - d = self._cdb.queryByAddress(key) - - d.addCallback(self._to_str) - d.addCallback(self.spit_result) - d.addErrback(log.err) - return d - except Exception as e: - log.err('exception in get: %r' % e) + log.msg("Query key: %s" % (key,)) + d = self._cdb.queryByAddress(key) + d.addCallback(self._getPubKey) + d.addErrback(log.err) + return d -- cgit v1.2.3 From b5309dc5910f35f5320c649be2ce2c6147030b39 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 25 Mar 2015 15:47:28 -0300 Subject: [refactor] separate tcp map server code Separate the common tcp map server code, used for both alias resolver and recipient access checker, to its own file. --- src/leap/mx/alias_resolver.py | 84 ++++++++++++++++++------------------------- 1 file changed, 34 insertions(+), 50 deletions(-) (limited to 'src/leap/mx/alias_resolver.py') diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 4247b57..9206ffb 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -24,65 +24,49 @@ Test this with postmap -v -q "foo" tcp:localhost:4242 TODO: o Look into using twisted.protocols.postfix.policies classes for controlling concurrent connections and throttling resource consumption. + o We should probably use twisted.mail.alias somehow. """ -try: - # TODO: we should probably use the system alias somehow - # from twisted.mail import alias - from twisted.protocols import postfix - from twisted.python import log - from twisted.internet import defer - from twisted.internet.protocol import ServerFactory -except ImportError: - print "This software requires Twisted. Please see the README file" - print "for instructions on getting required dependencies." +from twisted.protocols import postfix -class LEAPPostFixTCPMapserver(postfix.PostfixTCPMapServer): - def _cbGot(self, value): - uuid, pubkey = value - if uuid is None: - self.sendCode(500, postfix.quote("NOT FOUND SRY")) - elif pubkey is None: - self.sendCode(400, postfix.quote("4.7.13 USER ACCOUNT DISABLED")) - else: - self.sendCode(200, postfix.quote(value)) - +from leap.mx.tcp_map import LEAPostfixTCPMapServerFactory +from leap.mx.tcp_map import TCP_MAP_CODE_SUCCESS +from leap.mx.tcp_map import TCP_MAP_CODE_TEMPORARY_FAILURE +from leap.mx.tcp_map import TCP_MAP_CODE_PERMANENT_FAILURE -class AliasResolverFactory(ServerFactory): - protocol = LEAPPostFixTCPMapserver +class LEAPPostfixTCPMapAliasServer(postfix.PostfixTCPMapServer): + """ + A postfix tcp map alias resolver server. + """ - def __init__(self, couchdb): - self._cdb = couchdb - - def _to_str(self, result): - """ - Properly encodes the result string if any. + def _cbGot(self, value): """ - if isinstance(result, unicode): - result = result.encode("utf8") - if result is None: - log.msg("Result not found") - return result + Return a code and message depending on the result of the factory's + get(). - def _getPubKey(self, uuid): + :param value: The uuid and public key. + :type value: list + """ + uuid, pubkey = value if uuid is None: - return defer.succeed([None, None]) - d = defer.gatherResults([ - self._to_str(uuid), - self._cdb.getPubKey(uuid), - ]) - return d + self.sendCode( + TCP_MAP_CODE_PERMANENT_FAILURE, + postfix.quote("NOT FOUND SRY")) + elif pubkey is None: + self.sendCode( + TCP_MAP_CODE_TEMPORARY_FAILURE, + postfix.quote("4.7.13 USER ACCOUNT DISABLED")) + else: + self.sendCode( + TCP_MAP_CODE_SUCCESS, + postfix.quote(uuid)) - def get(self, key): - """ - Looks up the passed key, but only up to the username id of the key. - At some point we will have to consider the domain part too. - """ - log.msg("Query key: %s" % (key,)) - d = self._cdb.queryByAddress(key) - d.addCallback(self._getPubKey) - d.addErrback(log.err) - return d +class AliasResolverFactory(LEAPostfixTCPMapServerFactory): + """ + A factory for postfix tcp map alias resolver servers. + """ + + protocol = LEAPPostfixTCPMapAliasServer -- cgit v1.2.3 From 45adb4d6cfdb8b9ed11e3efc398d00ec6dbdc0b0 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 26 Mar 2015 15:25:50 -0300 Subject: [bug] limit pgp key lookup to access check server In order to minimize the number of couchdb queries and the number of mx lookups in case of junk mail this commit restricts the pgp key lookup to the access check server (and removes it from the alias server). Closes: #6795. --- src/leap/mx/alias_resolver.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) (limited to 'src/leap/mx/alias_resolver.py') diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 9206ffb..a139dd0 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -30,9 +30,8 @@ TODO: from twisted.protocols import postfix -from leap.mx.tcp_map import LEAPostfixTCPMapServerFactory +from leap.mx.tcp_map import LEAPPostfixTCPMapServerFactory from leap.mx.tcp_map import TCP_MAP_CODE_SUCCESS -from leap.mx.tcp_map import TCP_MAP_CODE_TEMPORARY_FAILURE from leap.mx.tcp_map import TCP_MAP_CODE_PERMANENT_FAILURE @@ -41,30 +40,25 @@ class LEAPPostfixTCPMapAliasServer(postfix.PostfixTCPMapServer): A postfix tcp map alias resolver server. """ - def _cbGot(self, value): + def _cbGot(self, uuid): """ Return a code and message depending on the result of the factory's get(). - :param value: The uuid and public key. + :param value: The uuid. :type value: list """ - uuid, pubkey = value if uuid is None: self.sendCode( TCP_MAP_CODE_PERMANENT_FAILURE, postfix.quote("NOT FOUND SRY")) - elif pubkey is None: - self.sendCode( - TCP_MAP_CODE_TEMPORARY_FAILURE, - postfix.quote("4.7.13 USER ACCOUNT DISABLED")) else: self.sendCode( TCP_MAP_CODE_SUCCESS, postfix.quote(uuid)) -class AliasResolverFactory(LEAPostfixTCPMapServerFactory): +class AliasResolverFactory(LEAPPostfixTCPMapServerFactory): """ A factory for postfix tcp map alias resolver servers. """ -- cgit v1.2.3 From b0ef529cc882a96903597fb5279919969fa286c3 Mon Sep 17 00:00:00 2001 From: drebs Date: Thu, 9 Apr 2015 17:18:37 -0300 Subject: [refactor] use couch reduced views for lookups The way uuid and pgp key were being queried by means of couch views was not efficient because they weren't using the reduce function and were filtering the views results in the python code. Also, the uuid is not actually needed to find out either if the address exists or if there's a pgp public key for that address. This commit refactors the couch helper to make use of the reduce functions in queried views and to get rid of the intermediate uuid querying. --- src/leap/mx/alias_resolver.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'src/leap/mx/alias_resolver.py') diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index a139dd0..dd631c8 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -40,22 +40,22 @@ class LEAPPostfixTCPMapAliasServer(postfix.PostfixTCPMapServer): A postfix tcp map alias resolver server. """ - def _cbGot(self, uuid): + def _cbGot(self, address): """ Return a code and message depending on the result of the factory's get(). - :param value: The uuid. - :type value: list + :param address: The address returned by the factory. + :type address: str """ - if uuid is None: + if address is None: self.sendCode( TCP_MAP_CODE_PERMANENT_FAILURE, postfix.quote("NOT FOUND SRY")) else: self.sendCode( TCP_MAP_CODE_SUCCESS, - postfix.quote(uuid)) + postfix.quote(address)) class AliasResolverFactory(LEAPPostfixTCPMapServerFactory): -- cgit v1.2.3 From 527d7d4a67f859a3315812b100b2c58fd0eeded6 Mon Sep 17 00:00:00 2001 From: drebs Date: Wed, 15 Apr 2015 12:48:34 -0300 Subject: [bug] return uuid as result of alias resolver This fixes a bug introduced on b0ef529cc882a96903597fb5279919969fa286c3, when the alias resolver was modified to return the user's address instead of the uuid. In order to fix this, I had to revert one of the changes made by the commit above, which is to don't make use of reduced view for the uuid query. The pgp public key query remains reduced, as implemented in the commit above. We also refactor the code a bit to allow for log messages specific to each of tcp map's sublasses. Related: #6858. --- src/leap/mx/alias_resolver.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) (limited to 'src/leap/mx/alias_resolver.py') diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index dd631c8..752eac4 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -40,22 +40,26 @@ class LEAPPostfixTCPMapAliasServer(postfix.PostfixTCPMapServer): A postfix tcp map alias resolver server. """ - def _cbGot(self, address): + def _cbGot(self, user_data): """ Return a code and message depending on the result of the factory's get(). - :param address: The address returned by the factory. - :type address: str + :param user_data: The user's uuid and pgp public key. + :type user_data: list """ - if address is None: + uuid, _ = user_data + if uuid is None: self.sendCode( TCP_MAP_CODE_PERMANENT_FAILURE, postfix.quote("NOT FOUND SRY")) else: + # properly encode uuid, otherwise twisted complains when replying + if isinstance(uuid, unicode): + uuid = uuid.encode("utf8") self.sendCode( TCP_MAP_CODE_SUCCESS, - postfix.quote(address)) + postfix.quote(uuid)) class AliasResolverFactory(LEAPPostfixTCPMapServerFactory): @@ -64,3 +68,7 @@ class AliasResolverFactory(LEAPPostfixTCPMapServerFactory): """ protocol = LEAPPostfixTCPMapAliasServer + + @property + def _query_message(self): + return "Resolving alias for" -- cgit v1.2.3 From 62def16809c1cf739db5a7a8e7aa24fec70fdf5d Mon Sep 17 00:00:00 2001 From: drebs Date: Fri, 17 Apr 2015 16:35:58 -0300 Subject: [doc] update documentation I'm updating (1) some very outdated doc from when the program was not yet written, and (2) some small stuff inside classes docstrings. --- src/leap/mx/alias_resolver.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/leap/mx/alias_resolver.py') diff --git a/src/leap/mx/alias_resolver.py b/src/leap/mx/alias_resolver.py index 752eac4..c6f2acc 100644 --- a/src/leap/mx/alias_resolver.py +++ b/src/leap/mx/alias_resolver.py @@ -19,6 +19,12 @@ """ Classes for resolving postfix aliases. +The resolver is queried by the mail server before delivery to the mail spool +directory, and should return the user uuid. This way, we get rid from the user +address early and the mail server will delivery the message to +"@". Later, the mail receiver part of MX will parse the +"Delivered-To" header to extract the uuid and fetch the user's pgp public key. + Test this with postmap -v -q "foo" tcp:localhost:4242 TODO: -- cgit v1.2.3