1# Copyright (C) 1998-2018 by the Free Software Foundation, Inc. 2# 3# This program is free software; you can redistribute it and/or 4# modify it under the terms of the GNU General Public License 5# as published by the Free Software Foundation; either version 2 6# of the License, or (at your option) any later version. 7# 8# This program is distributed in the hope that it will be useful, 9# but WITHOUT ANY WARRANTY; without even the implied warranty of 10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11# GNU General Public License for more details. 12# 13# You should have received a copy of the GNU General Public License 14# along with this program; if not, write to the Free Software 15# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 16# USA. 17 18"""Parse RFC 3464 (i.e. DSN) bounce formats. 19 20RFC 3464 obsoletes 1894 which was the old DSN standard. This module has not 21been audited for differences between the two. 22""" 23 24from email.Iterators import typed_subpart_iterator 25from email.Utils import parseaddr 26from cStringIO import StringIO 27 28from Mailman.Bouncers.BouncerAPI import Stop 29 30try: 31 True, False 32except NameError: 33 True = 1 34 False = 0 35 36 37 38def process(msg): 39 # Iterate over each message/delivery-status subpart 40 addrs = [] 41 for part in typed_subpart_iterator(msg, 'message', 'delivery-status'): 42 if not part.is_multipart(): 43 # Huh? 44 continue 45 # Each message/delivery-status contains a list of Message objects 46 # which are the header blocks. Iterate over those too. 47 for msgblock in part.get_payload(): 48 # We try to dig out the Original-Recipient (which is optional) and 49 # Final-Recipient (which is mandatory, but may not exactly match 50 # an address on our list). Some MTA's also use X-Actual-Recipient 51 # as a synonym for Original-Recipient, but some apparently use 52 # that for other purposes :( 53 # 54 # Also grok out Action so we can do something with that too. 55 action = msgblock.get('action', '').lower() 56 # Some MTAs have been observed that put comments on the action. 57 if action.startswith('delayed'): 58 return Stop 59 # opensmtpd uses non-compliant Action: error. 60 if not (action.startswith('fail') or action.startswith('error')): 61 # Some non-permanent failure, so ignore this block 62 continue 63 params = [] 64 foundp = False 65 for header in ('original-recipient', 'final-recipient'): 66 for k, v in msgblock.get_params([], header): 67 if k.lower() == 'rfc822': 68 foundp = True 69 else: 70 params.append(k) 71 if foundp: 72 # Note that params should already be unquoted. 73 addrs.extend(params) 74 break 75 else: 76 # MAS: This is a kludge, but SMTP-GATEWAY01.intra.home.dk 77 # has a final-recipient with an angle-addr and no 78 # address-type parameter at all. Non-compliant, but ... 79 for param in params: 80 if param.startswith('<') and param.endswith('>'): 81 addrs.append(param[1:-1]) 82 # Uniquify 83 rtnaddrs = {} 84 for a in addrs: 85 if a is not None: 86 realname, a = parseaddr(a) 87 rtnaddrs[a] = True 88 return rtnaddrs.keys() 89