1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
# Copyright (C) 2001,2002 by the Free Software Foundation, Inc.
#
# This program 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 2
# of the License, or (at your option) any later version.
#
# This program 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 this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
"""Bounce queue runner."""
import re
from email.Utils import parseaddr
from Mailman import mm_cfg
from Mailman import Utils
from Mailman import LockFile
from Mailman.Bouncers import BouncerAPI
from Mailman.Queue.Runner import Runner
from Mailman.Queue.sbcache import get_switchboard
from Mailman.Logging.Syslog import syslog
class BounceRunner(Runner):
QDIR = mm_cfg.BOUNCEQUEUE_DIR
# We only do bounce processing once per minute.
SLEEPTIME = 60
def _dispose(self, mlist, msg, msgdata):
outq = get_switchboard(mm_cfg.OUTQUEUE_DIR)
# There are a few possibilities here:
#
# - the message could have been VERP'd in which case, we know exactly
# who the message was destined for. That make our job easy.
# - the message could have been originally destined for a list owner,
# but a list owner address itself bounced. That's bad, and for now
# we'll simply log the problem and attempt to deliver the message to
# the site owner.
#
# All messages to list-owner@vdom.ain have their envelope sender set
# to site-owner@dom.ain (no virtual domain). Is this a bounce for a
# message to a list owner, coming to the site owner?
if msg.get('to', '') == Utils.get_site_email(extra='-owner'):
# Send it on to the site owners, but craft the envelope sender to
# be the -loop detection address, so if /they/ bounce, we won't
# get stuck in a bounce loop.
outq.enqueue(msg, msgdata,
recips=[Utils.get_site_email()],
envsender=Utils.get_site_email(extra='loop'),
)
# List isn't doing bounce processing?
if not mlist.bounce_processing:
return
# Try VERP detection first, since it's quick and easy
addrs = verp_bounce(mlist, msg)
if not addrs:
# That didn't give us anything useful, so try the old fashion
# bounce matching modules
addrs = BouncerAPI.ScanMessages(mlist, msg)
# If that still didn't return us any useful addresses, then send it on
# or discard it.
if not addrs:
# Does the list owner want to get non-matching bounce messages?
# If not, simply discard it.
if mlist.bounce_unrecognized_goes_to_list_owner:
syslog('bounce', 'forwarding unrecognized, message-id: %s',
msg.get('message-id', 'n/a'))
# Be sure to point the envelope sender at the site owner for
# any bounces to list owners.
recips = mlist.owner[:]
recips.extend(mlist.moderator)
outq.enqueue(msg, msgdata,
recips=recips,
envsender=Utils.get_site_email(extra='admin'),
)
else:
syslog('bounce', 'discarding unrecognized, message-id: %s',
msg.get('message-id', 'n/a'))
return
# Okay, we have some recognized addresses. We now need to register
# the bounces for each of these. If the bounce came to the site list,
# then we'll register the address on every list in the system, but
# note: this could be VERY resource intensive!
if mlist.internal_name() == mm_cfg.MAILMAN_SITE_LIST:
for listname in Utils.list_names():
xlist = self._open_list(listname)
if xlist.isMember(addr):
unlockp = 0
if not xlist.Locked():
try:
xlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
except LockFile.TimeOutError:
# Oh well, forget aboutf this list
continue
unlockp = 1
try:
xlist.registerBounce(addr, msg)
found = 1
xlist.Save()
finally:
if unlockp:
xlist.Unlock()
else:
try:
mlist.Lock(timeout=mm_cfg.LIST_LOCK_TIMEOUT)
except LockFile.TimeOutError:
# Oh well, forget about this bounce
return
try:
for addr in addrs:
mlist.registerBounce(addr, msg)
mlist.Save()
finally:
mlist.Unlock()
def verp_bounce(mlist, msg):
bmailbox, bdomain = Utils.ParseEmail(mlist.GetBouncesEmail())
# Sadly not every MTA bounces VERP messages correctly. Fall back to
# Delivered-to: and Apparently-To:, and then short-circuit if we still
# don't have anything to work with. Note that there can be multiple
# Delivered-To: headers so we need to search them all (and we don't
# worry about false positives for forwarded email, because only one
# should match VERP_REGEXP).
vals = []
for header in ('to', 'delivered-to', 'apparently-to'):
vals.extend(msg.get_all(header, []))
for field in vals:
to = parseaddr(field)[1]
if not to:
continue # empty header
mo = re.search(mm_cfg.VERP_REGEXP, to)
if not mo:
continue # no match of regexp
try:
if bmailbox <> mo.group('bounces'):
continue # not a bounce to our list
# All is good
addr = '%s@%s' % mo.group('mailbox', 'host')
except IndexError:
syslog('error',
"VERP_REGEXP doesn't yield the right match groups: %s",
mm_cfg.VERP_REGEXP)
return []
return [addr]
|