diff options
Diffstat (limited to '')
-rw-r--r-- | Mailman/Bouncers/DSN.py | 79 |
1 files changed, 79 insertions, 0 deletions
diff --git a/Mailman/Bouncers/DSN.py b/Mailman/Bouncers/DSN.py new file mode 100644 index 00000000..3e040bef --- /dev/null +++ b/Mailman/Bouncers/DSN.py @@ -0,0 +1,79 @@ +# Copyright (C) 1998,1999,2000,2001,2002 by the Free Software Foundation, Inc. +# +# 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 2 +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +"""Parse RFC 1894 (i.e. DSN) bounce formats.""" + +from email.Iterators import typed_subpart_iterator +from email.Utils import parseaddr +from cStringIO import StringIO + + + +def check(msg): + # Iterate over each message/delivery-status subpart + addrs = [] + 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', '') + # BAW: Should we treat delayed bounces the same? Yes, because if + # the transient problem clears up, they should get unbounced. The + # other problem is what to do about a DSN that has both delayed + # and failed actions in multiple header blocks? We're not + # architected to handle that. ;/ + if action.lower() not in ('failed', 'failure', 'delayed'): + # Some non-permanent failure, so ignore this block + continue + params = [] + foundp = 0 + for header in ('original-recipient', 'final-recipient'): + for k, v in msgblock.get_params([], header): + if k.lower() == 'rfc822': + foundp = 1 + else: + params.append(k) + if foundp: + # Note that params should already be unquoted. + addrs.extend(params) + break + # Uniquify + rtnaddrs = {} + for a in addrs: + if a is not None: + realname, a = parseaddr(a) + rtnaddrs[a] = 1 + return rtnaddrs.keys() + + + +def process(msg): + # 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_subtype() <> 'report': + return None + return check(msg) |