diff options
| author | Barry Warsaw | 2010-08-08 10:49:55 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2010-08-08 10:49:55 -0400 |
| commit | 79cc1bc34c9386058a9f0734ab9ad7fad3b637b5 (patch) | |
| tree | 0252af62b19a0edbd311693b303b12bc1f2e9868 /src/mailman/bouncers/dsn.py | |
| parent | 83f78ec26541ab0c7b91794ab6b3bc1d8285f9a9 (diff) | |
| download | mailman-79cc1bc34c9386058a9f0734ab9ad7fad3b637b5.tar.gz mailman-79cc1bc34c9386058a9f0734ab9ad7fad3b637b5.tar.zst mailman-79cc1bc34c9386058a9f0734ab9ad7fad3b637b5.zip | |
Diffstat (limited to 'src/mailman/bouncers/dsn.py')
| -rw-r--r-- | src/mailman/bouncers/dsn.py | 108 |
1 files changed, 108 insertions, 0 deletions
diff --git a/src/mailman/bouncers/dsn.py b/src/mailman/bouncers/dsn.py new file mode 100644 index 000000000..f9d15bbd2 --- /dev/null +++ b/src/mailman/bouncers/dsn.py @@ -0,0 +1,108 @@ +# Copyright (C) 1998-2010 by the Free Software Foundation, Inc. +# +# This file is part of GNU Mailman. +# +# GNU Mailman 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. +# +# GNU Mailman 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 +# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. + +"""Parse RFC 3464 (i.e. DSN) bounce formats. + +RFC 3464 obsoletes 1894 which was the old DSN standard. This module has not +been audited for differences between the two. +""" + +from __future__ import absolute_import, unicode_literals + +__metaclass__ = type +__all__ = [ + 'DSN', + ] + + +from email.iterators import typed_subpart_iterator +from email.utils import parseaddr +from zope.interface import implements + +from mailman.interfaces.bounce import IBounceDetector, NonFatal + + + +def check(msg): + # Iterate over each message/delivery-status subpart. + addresses = [] + for part in typed_subpart_iterator(msg, 'message', 'delivery-status'): + if not part.is_multipart(): + # Huh? + continue + # Each message/delivery-status contains a list of Message objects + # which are the header blocks. Iterate over those too. + for msgblock in part.get_payload(): + # We try to dig out the Original-Recipient (which is optional) and + # Final-Recipient (which is mandatory, but may not exactly match + # an address on our list). Some MTA's also use X-Actual-Recipient + # as a synonym for Original-Recipient, but some apparently use + # that for other purposes :( + # + # Also grok out Action so we can do something with that too. + action = msgblock.get('action', '').lower() + # Some MTAs have been observed that put comments on the action. + if action.startswith('delayed'): + return NonFatal + if not action.startswith('fail'): + # Some non-permanent failure, so ignore this block. + continue + params = [] + foundp = False + for header in ('original-recipient', 'final-recipient'): + for k, v in msgblock.get_params([], header): + if k.lower() == 'rfc822': + foundp = True + else: + params.append(k) + if foundp: + # Note that params should already be unquoted. + addresses.extend(params) + break + else: + # MAS: This is a kludge, but SMTP-GATEWAY01.intra.home.dk + # has a final-recipient with an angle-addr and no + # address-type parameter at all. Non-compliant, but ... + for param in params: + if param.startswith('<') and param.endswith('>'): + addresses.append(param[1:-1]) + return set(parseaddr(address)[1] for address in addresses + if address is not None) + + + +class DSN: + """Parse RFC 3464 (i.e. DSN) bounce formats.""" + + implements(IBounceDetector) + + def process(self, msg): + # A DSN has been seen wrapped with a "legal disclaimer" by an outgoing + # MTA in a multipart/mixed outer part. + if msg.is_multipart() and msg.get_content_subtype() == 'mixed': + msg = msg.get_payload()[0] + # The above will suffice if the original message 'parts' were wrapped + # with the disclaimer added, but the original DSN can be wrapped as a + # message/rfc822 part. We need to test that too. + if msg.is_multipart() and msg.get_content_type() == 'message/rfc822': + msg = msg.get_payload()[0] + # The report-type parameter should be "delivery-status", but it seems + # that some DSN generating MTAs don't include this on the + # Content-Type: header, so let's relax the test a bit. + if not msg.is_multipart() or msg.get_content_subtype() <> 'report': + return None + return check(msg) |
