# 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)