~futatuki/mailman/2.1-forbid-subscription

« back to all changes in this revision

Viewing changes to Mailman/Queue/BounceRunner.py

  • Committer:
  • Date: 2003-01-02 05:25:50 UTC
  • Revision ID: vcs-imports@canonical.com-20030102052550-qqbl1i96tzg3bach
This commit was manufactured by cvs2svn to create branch
'Release_2_1-maint'.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2001,2002 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 
16
 
 
17
"""Bounce queue runner."""
 
18
 
 
19
import re
 
20
from email.MIMEText import MIMEText
 
21
from email.MIMEMessage import MIMEMessage
 
22
from email.Utils import parseaddr
 
23
 
 
24
from Mailman import mm_cfg
 
25
from Mailman import Utils
 
26
from Mailman import LockFile
 
27
from Mailman.Message import UserNotification
 
28
from Mailman.Bouncers import BouncerAPI
 
29
from Mailman.Queue.Runner import Runner
 
30
from Mailman.Queue.sbcache import get_switchboard
 
31
from Mailman.Logging.Syslog import syslog
 
32
from Mailman.i18n import _
 
33
 
 
34
COMMASPACE = ', '
 
35
 
 
36
 
 
37
 
 
38
class BounceRunner(Runner):
 
39
    QDIR = mm_cfg.BOUNCEQUEUE_DIR
 
40
    # We only do bounce processing once per minute.
 
41
    SLEEPTIME = mm_cfg.minutes(1)
 
42
 
 
43
    def _dispose(self, mlist, msg, msgdata):
 
44
        # Make sure we have the most up-to-date state
 
45
        mlist.Load()
 
46
        outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
 
47
        # There are a few possibilities here:
 
48
        #
 
49
        # - the message could have been VERP'd in which case, we know exactly
 
50
        #   who the message was destined for.  That make our job easy.
 
51
        # - the message could have been originally destined for a list owner,
 
52
        #   but a list owner address itself bounced.  That's bad, and for now
 
53
        #   we'll simply log the problem and attempt to deliver the message to
 
54
        #   the site owner.
 
55
        #
 
56
        # All messages to list-owner@vdom.ain have their envelope sender set
 
57
        # to site-owner@dom.ain (no virtual domain).  Is this a bounce for a
 
58
        # message to a list owner, coming to the site owner?
 
59
        if msg.get('to', '') == Utils.get_site_email(extra='-owner'):
 
60
            # Send it on to the site owners, but craft the envelope sender to
 
61
            # be the -loop detection address, so if /they/ bounce, we won't
 
62
            # get stuck in a bounce loop.
 
63
            outq.enqueue(msg, msgdata,
 
64
                         recips=[Utils.get_site_email()],
 
65
                         envsender=Utils.get_site_email(extra='loop'),
 
66
                         )
 
67
        # List isn't doing bounce processing?
 
68
        if not mlist.bounce_processing:
 
69
            return
 
70
        # Try VERP detection first, since it's quick and easy
 
71
        addrs = verp_bounce(mlist, msg)
 
72
        if not addrs:
 
73
            # That didn't give us anything useful, so try the old fashion
 
74
            # bounce matching modules
 
75
            addrs = BouncerAPI.ScanMessages(mlist, msg)
 
76
        # If that still didn't return us any useful addresses, then send it on
 
77
        # or discard it.
 
78
        if not addrs:
 
79
            syslog('bounce', 'bounce message w/no discernable addresses: %s',
 
80
                   msg.get('message-id'))
 
81
            maybe_forward(mlist, msg)
 
82
            return
 
83
        # BAW: It's possible that there are None's in the list of addresses,
 
84
        # although I'm unsure how that could happen.  Possibly ScanMessages()
 
85
        # can let None's sneak through.  In any event, this will kill them.
 
86
        addrs = filter(None, addrs)
 
87
        # Okay, we have some recognized addresses.  We now need to register
 
88
        # the bounces for each of these.  If the bounce came to the site list,
 
89
        # then we'll register the address on every list in the system, but
 
90
        # note: this could be VERY resource intensive!
 
91
        foundp = 0
 
92
        listname = mlist.internal_name()
 
93
        if listname == mm_cfg.MAILMAN_SITE_LIST:
 
94
            foundp = 1
 
95
            for listname in Utils.list_names():
 
96
                xlist = self._open_list(listname)
 
97
                xlist.Load()
 
98
                for addr in addrs:
 
99
                    if xlist.isMember(addr):
 
100
                        unlockp = 0
 
101
                        if not xlist.Locked():
 
102
                            try:
 
103
                                xlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
 
104
                            except LockFile.TimeOutError:
 
105
                                # Oh well, forget aboutf this list
 
106
                                continue
 
107
                            unlockp = 1
 
108
                        try:
 
109
                            xlist.registerBounce(addr, msg)
 
110
                            foundp = 1
 
111
                            xlist.Save()
 
112
                        finally:
 
113
                            if unlockp:
 
114
                                xlist.Unlock()
 
115
        else:
 
116
            try:
 
117
                mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
 
118
            except LockFile.TimeOutError:
 
119
                # Try again later
 
120
                syslog('bounce', "%s: couldn't get list lock", listname)
 
121
                return 1
 
122
            else:
 
123
                try:
 
124
                    for addr in addrs:
 
125
                        if mlist.isMember(addr):
 
126
                            mlist.registerBounce(addr, msg)
 
127
                            foundp = 1
 
128
                    mlist.Save()
 
129
                finally:
 
130
                    mlist.Unlock()
 
131
        if not foundp:
 
132
            # It means an address was recognized but it wasn't an address
 
133
            # that's on any mailing list at this site.  BAW: don't forward
 
134
            # these, but do log it.
 
135
            syslog('bounce', 'bounce message with non-members of %s: %s',
 
136
                   listname, COMMASPACE.join(addrs))
 
137
            #maybe_forward(mlist, msg)
 
138
 
 
139
 
 
140
 
 
141
def verp_bounce(mlist, msg):
 
142
    bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail())
 
143
    # Sadly not every MTA bounces VERP messages correctly, or consistently.
 
144
    # Fall back to Delivered-To: (Postfix), Envelope-To: (Exim) and
 
145
    # Apparently-To:, and then short-circuit if we still don't have anything
 
146
    # to work with.  Note that there can be multiple Delivered-To: headers so
 
147
    # we need to search them all (and we don't worry about false positives for
 
148
    # forwarded email, because only one should match VERP_REGEXP).
 
149
    vals = []
 
150
    for header in ('to', 'delivered-to', 'envelope-to', 'apparently-to'):
 
151
        vals.extend(msg.get_all(header, []))
 
152
    for field in vals:
 
153
        to = parseaddr(field)[1]
 
154
        if not to:
 
155
            continue                          # empty header
 
156
        mo = re.search(mm_cfg.VERP_REGEXP, to)
 
157
        if not mo:
 
158
            continue                          # no match of regexp
 
159
        try:
 
160
            if bmailbox <> mo.group('bounces'):
 
161
                continue                      # not a bounce to our list
 
162
            # All is good
 
163
            addr = '%s@%s' % mo.group('mailbox', 'host')
 
164
        except IndexError:
 
165
            syslog('error',
 
166
                   "VERP_REGEXP doesn't yield the right match groups: %s",
 
167
                   mm_cfg.VERP_REGEXP)
 
168
            return []
 
169
        return [addr]
 
170
 
 
171
 
 
172
 
 
173
def maybe_forward(mlist, msg):
 
174
    # Does the list owner want to get non-matching bounce messages?
 
175
    # If not, simply discard it.
 
176
    if mlist.bounce_unrecognized_goes_to_list_owner:
 
177
        adminurl = mlist.GetScriptURL('admin', absolute=1) + '/bounce'
 
178
        mlist.ForwardMessage(msg,
 
179
                             text=_("""\
 
180
The attached message was received as a bounce, but either the bounce format
 
181
was not recognized, or no member addresses could be extracted from it.  This
 
182
mailing list has been configured to send all unrecognized bounce messages to
 
183
the list administrator(s).
 
184
 
 
185
For more information see:
 
186
%(adminurl)s
 
187
 
 
188
"""),
 
189
                             subject=_('Uncaught bounce notification'),
 
190
                             tomoderators=0)
 
191
        syslog('bounce', 'forwarding unrecognized, message-id: %s',
 
192
               msg.get('message-id', 'n/a'))
 
193
    else:
 
194
        syslog('bounce', 'discarding unrecognized, message-id: %s',
 
195
               msg.get('message-id', 'n/a'))