~nkarageuzian/mailman/usermanagement

« back to all changes in this revision

Viewing changes to src/mailman/bouncers/qmail.py

  • Committer: Barry Warsaw
  • Date: 2011-07-15 22:55:19 UTC
  • mfrom: (7027.1.1 refactor)
  • Revision ID: barry@list.org-20110715225519-3q8qr1oyv9rqfcd1
Factor out bounce detection to `flufl.bounce`.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 1998-2011 by the Free Software Foundation, Inc.
2
 
#
3
 
# This file is part of GNU Mailman.
4
 
#
5
 
# GNU Mailman is free software: you can redistribute it and/or modify it under
6
 
# the terms of the GNU General Public License as published by the Free
7
 
# Software Foundation, either version 3 of the License, or (at your option)
8
 
# any later version.
9
 
#
10
 
# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11
 
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12
 
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
13
 
# more details.
14
 
#
15
 
# You should have received a copy of the GNU General Public License along with
16
 
# GNU Mailman.  If not, see <http://www.gnu.org/licenses/>.
17
 
 
18
 
"""Parse bounce messages generated by qmail.
19
 
 
20
 
Qmail actually has a standard, called QSBMF (qmail-send bounce message
21
 
format), as described in
22
 
 
23
 
    http://cr.yp.to/proto/qsbmf.txt
24
 
 
25
 
This module should be conformant.
26
 
 
27
 
"""
28
 
 
29
 
from __future__ import absolute_import, unicode_literals
30
 
 
31
 
__metaclass__ = type
32
 
__all__ = [
33
 
    'Qmail',
34
 
    ]
35
 
 
36
 
 
37
 
import re
38
 
 
39
 
from email.iterators import body_line_iterator
40
 
from flufl.enum import Enum
41
 
from zope.interface import implements
42
 
 
43
 
from mailman.interfaces.bounce import IBounceDetector
44
 
 
45
 
 
46
 
# Other (non-standard?) intros have been observed in the wild.
47
 
introtags = [
48
 
    "We're sorry. There's a problem",
49
 
    'Check your send e-mail address.',
50
 
    'Hi. The MTA program at',
51
 
    'Hi. This is the',
52
 
    'This is the mail delivery agent at',
53
 
    'Unfortunately, your mail was not delivered',
54
 
    ]
55
 
acre = re.compile(r'<(?P<addr>[^>]*)>:')
56
 
 
57
 
 
58
 
class ParseState(Enum):
59
 
    start = 0
60
 
    intro_paragraph_seen = 1
61
 
    recip_paragraph_seen = 2
62
 
 
63
 
 
64
 
 
65
 
class Qmail:
66
 
    """Parse QSBMF format bounces."""
67
 
 
68
 
    implements(IBounceDetector)
69
 
 
70
 
    def process(self, msg):
71
 
        """See `IBounceDetector`."""
72
 
        addresses = set()
73
 
        state = ParseState.start
74
 
        for line in body_line_iterator(msg):
75
 
            line = line.strip()
76
 
            if state is ParseState.start:
77
 
                for introtag in introtags:
78
 
                    if line.startswith(introtag):
79
 
                        state = ParseState.intro_paragraph_seen
80
 
                        break
81
 
            elif state is ParseState.intro_paragraph_seen and not line:
82
 
                # Looking for the end of the intro paragraph.
83
 
                state = ParseState.recip_paragraph_seen
84
 
            elif state is ParseState.recip_paragraph_seen:
85
 
                if line.startswith('-'):
86
 
                    # We're looking at the break paragraph, so we're done.
87
 
                    break
88
 
                # At this point we know we must be looking at a recipient
89
 
                # paragraph.
90
 
                mo = acre.match(line)
91
 
                if mo:
92
 
                    addresses.add(mo.group('addr'))
93
 
                # Otherwise, it must be a continuation line, so just ignore it.
94
 
            else:
95
 
                # We're not looking at anything in particular.
96
 
                pass
97
 
        return addresses