13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17
"""Incoming queue runner."""
19
# A typical Mailman list exposes nine aliases which point to seven different
20
# wrapped scripts. E.g. for a list named `mylist', you'd have:
22
# mylist-bounces -> bounces (-admin is a deprecated alias)
23
# mylist-confirm -> confirm
24
# mylist-join -> join (-subscribe is an alias)
25
# mylist-leave -> leave (-unsubscribe is an alias)
26
# mylist-owner -> owner
28
# mylist-request -> request
30
# -request, -join, and -leave are a robot addresses; their sole purpose is to
31
# process emailed commands in a Majordomo-like fashion (although the latter
32
# two are hardcoded to subscription and unsubscription requests). -bounces is
33
# the automated bounce processor, and all messages to list members have their
34
# return address set to -bounces. If the bounce processor fails to extract a
35
# bouncing member address, it can optionally forward the message on to the
38
# -owner is for reaching a human operator with minimal list interaction
39
# (i.e. no bounce processing). -confirm is another robot address which
40
# processes replies to VERP-like confirmation notices.
42
# So delivery flow of messages look like this:
44
# joerandom ---> mylist ---> list members
47
# | mylist-bounces <---+ <-------------------------------+
49
# | +--->[internal bounce processing] |
51
# | | | [bounce found] |
52
# | [bounces *] +--->[register and discard] |
55
# | [list owners] |[no bounce found] | |
58
# +-------> mylist-owner <--------+ | |
60
# | data/owner-bounces.mbox <--[site list] <---+ |
62
# +-------> mylist-join--+ |
64
# +------> mylist-leave--+ |
67
# +-------> mylist-request |
69
# | +---> [command processor] |
71
# +-----> mylist-confirm ----> +---> joerandom |
74
# +----------------------+
76
# A person can send an email to the list address (for posting), the -owner
77
# address (to reach the human operator), or the -confirm, -join, -leave, and
78
# -request mailbots. Message to the list address are then forwarded on to the
79
# list membership, with bounces directed to the -bounces address.
81
# [*] Messages sent to the -owner address are forwarded on to the list
82
# owner/moderators. All -owner destined messages have their bounces directed
83
# to the site list -bounces address, regardless of whether a human sent the
84
# message or the message was crafted internally. The intention here is that
85
# the site owners want to be notified when one of their list owners' addresses
86
# starts bouncing (yes, the will be automated in a future release).
88
# Any messages to site owners has their bounces directed to a special
89
# "loop-killer" address, which just dumps the message into
90
# data/owners-bounces.mbox.
92
# Finally, message to any of the mailbots causes the requested action to be
93
# performed. Results notifications are sent to the author of the message,
94
# which all bounces pointing back to the -bounces address.
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
18
"""Incoming queue runner.
20
This runner's sole purpose in life is to decide the disposition of the
21
message. It can either be accepted for delivery, rejected (i.e. bounced),
22
held for moderator approval, or discarded.
24
When accepted, the message is forwarded on to the `prep queue` where it is
25
prepared for delivery. Rejections, discards, and holds are processed
102
from cStringIO import StringIO
104
from Mailman import Errors
31
from Mailman.app.chains import process
105
32
from Mailman.configuration import config
106
33
from Mailman.queue import Runner
108
log = logging.getLogger('mailman.error')
109
vlog = logging.getLogger('mailman.vette')
113
37
class IncomingRunner(Runner):
116
40
def _dispose(self, mlist, msg, msgdata):
117
41
if msgdata.get('envsender') is None:
118
msg['envsender'] = mlist.no_reply_address
119
# Process the message through a handler pipeline. The handler
120
# pipeline can actually come from one of three places: the message
121
# metadata, the mlist, or the global pipeline.
123
# If a message was requeued due to an uncaught exception, its metadata
124
# will contain the retry pipeline. Use this above all else.
125
# Otherwise, if the mlist has a `pipeline' attribute, it should be
126
# used. Final fallback is the global pipeline.
127
pipeline = self._get_pipeline(mlist, msg, msgdata)
128
msgdata['pipeline'] = pipeline
129
more = self._dopipeline(mlist, msg, msgdata, pipeline)
131
del msgdata['pipeline']
136
def _get_pipeline(self, mlist, msg, msgdata):
137
# We must return a copy of the list, otherwise, the first message that
138
# flows through the pipeline will empty it out!
139
return msgdata.get('pipeline',
140
getattr(mlist, 'pipeline',
141
config.GLOBAL_PIPELINE))[:]
143
def _dopipeline(self, mlist, msg, msgdata, pipeline):
145
handler = pipeline.pop(0)
146
modname = 'Mailman.Handlers.' + handler
150
sys.modules[modname].process(mlist, msg, msgdata)
151
# Failsafe -- a child may have leaked through.
152
if pid <> os.getpid():
153
log.error('child process leaked thru: %s', modname)
155
except Errors.DiscardMessage:
156
# Throw the message away; we need do nothing else with it.
157
vlog.info('Message discarded, msgid: %s',
158
msg.get('message-id', 'n/a'))
160
except Errors.HoldMessage:
161
# Let the approval process take it from here. The message no
162
# longer needs to be queued.
164
except Errors.RejectMessage, e:
165
mlist.bounce_message(msg, e)
168
# Push this pipeline module back on the stack, then re-raise
170
pipeline.insert(0, handler)
172
# We've successfully completed handling of this message
42
msgdata['envsender'] = mlist.no_reply_address
43
# Process the message through the mailing list's start chain.
44
process(mlist, msg, msgdata, mlist.start_chain)
45
# Do not keep this message queued.