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