Netscape.py 2.76 KB
Newer Older
Barry Warsaw's avatar
Barry Warsaw committed
1
# Copyright (C) 1998-2009 by the Free Software Foundation, Inc.
2
#
Barry Warsaw's avatar
Barry Warsaw committed
3
# This file is part of GNU Mailman.
bwarsaw's avatar
bwarsaw committed
4
#
Barry Warsaw's avatar
Barry Warsaw committed
5 6 7 8
# 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.
bwarsaw's avatar
bwarsaw committed
9
#
Barry Warsaw's avatar
Barry Warsaw committed
10 11 12 13 14 15 16
# 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/>.
17 18 19 20

"""Netscape Messaging Server bounce formats.

I've seen at least one NMS server version 3.6 (envy.gmp.usyd.edu.au) bounce
bwarsaw's avatar
bwarsaw committed
21
messages of this format.  Bounces come in DSN MIME format, but don't include
22
any -Recipient: headers.  Gotta just parse the text :(
23 24 25 26

NMS 4.1 (dfw-smtpin1.email.verio.net) seems even worse, but we'll try to
decipher the format here too.

27 28 29
"""

import re
bwarsaw's avatar
bwarsaw committed
30
from cStringIO import StringIO
31

32 33 34 35 36 37 38
pcre = re.compile(
    r'This Message was undeliverable due to the following reason:',
    re.IGNORECASE)

acre = re.compile(
    r'(?P<reply>please reply to)?.*<(?P<addr>[^>]*)>',
    re.IGNORECASE)
39 40


bwarsaw's avatar
bwarsaw committed
41 42 43

def flatten(msg, leaves):
    # give us all the leaf (non-multipart) subparts
44
    if msg.is_multipart():
bwarsaw's avatar
bwarsaw committed
45 46 47 48 49 50
        for part in msg.get_payload():
            flatten(part, leaves)
    else:
        leaves.append(msg)


51 52

def process(msg):
53 54 55 56
    # Sigh.  Some show NMS 3.6's show
    #     multipart/report; report-type=delivery-status
    # and some show
    #     multipart/mixed;
57
    if not msg.is_multipart():
58
        return None
bwarsaw's avatar
bwarsaw committed
59 60
    # We're looking for a text/plain subpart occuring before a
    # message/delivery-status subpart.
61
    plainmsg = None
bwarsaw's avatar
bwarsaw committed
62 63 64
    leaves = []
    flatten(msg, leaves)
    for i, subpart in zip(range(len(leaves)-1), leaves):
65
        if subpart.get_content_type() == 'text/plain':
bwarsaw's avatar
bwarsaw committed
66
            plainmsg = subpart
67 68 69 70
            break
    if not plainmsg:
        return None
    # Total guesswork, based on captured examples...
bwarsaw's avatar
bwarsaw committed
71
    body = StringIO(plainmsg.get_payload())
72 73
    addrs = []
    while 1:
bwarsaw's avatar
bwarsaw committed
74
        line = body.readline()
75 76 77 78
        if not line:
            break
        mo = pcre.search(line)
        if mo:
79 80 81 82
            # We found a bounce section, but I have no idea what the official
            # format inside here is.  :(  We'll just search for <addr>
            # strings.
            while 1:
bwarsaw's avatar
bwarsaw committed
83
                line = body.readline()
84 85 86 87 88
                if not line:
                    break
                mo = acre.search(line)
                if mo and not mo.group('reply'):
                    addrs.append(mo.group('addr'))
bwarsaw's avatar
bwarsaw committed
89
    return addrs