~barry/mailman/events-and-web

« back to all changes in this revision

Viewing changes to Mailman/queue/incoming.py

  • Committer: Barry Warsaw
  • Date: 2008-02-03 04:03:19 UTC
  • mfrom: (6581.1.27 rules)
  • Revision ID: barry@python.org-20080203040319-mnb1sar9bumaih01
Merge the 'rules' branch.

Give the first alpha a code name.

This branch mostly gets rid of all the approval oriented handlers in favor of
a chain-of-rules based approach.  This will be much more powerful and
extensible, allowing rule definition by plugin and chain creation via web
page.

When a message is processed by the incoming queue, it gets sent through a
chain of rules.  The starting chain is defined on the mailing list object, and
there is a built-in default starting chain, called 'built-in'.  Each chain is
made up of links, which describe a rule and an action, along with possibly
some other information.  Actions allow processing to take a detour through
another chain, jump to another chain, stop processing, run a function, etc.

The built-in chain essentially implements the original early part of the
handler pipeline.  If a message makes it through the built-in chain, it gets
sent to the prep queue, where the message is decorated and such before sending
out to the list membership.  The 'accept' chain is what moves the message into
the prep queue.

There are also 'hold', 'discard', and 'reject' chains, which do what you would
expect them to.  There are lots of built-in rules, implementing everything
from the old emergency handler to new handlers such as one not allowing empty
subject headers.

IMember grows an is_moderated attribute.

The 'adminapproved' metadata key is renamed 'moderator_approved'.

Fix some bogus uses of noreply_address to no_reply_address.

Stash an 'original_size' attribute on the message after parsing its plain
text.  This can be used later to ensure the original message does not exceed a
specified size without have to flatten the message again.

The KNOWN_SPAMMERS global variable is replaced with HEADER_MATCHES.  The
mailing list's header_filter_rules variable is replaced with header_matches
which has the same semantics as HEADER_MATCHES, but is list-specific.

DEFAULT_MAIL_COMMANDS_MAX_LINES -> EMAIL_COMMANDS_MAX_LINES.

Update smtplistener.py to be much better, to use maildir format instead of
mbox format, to respond to RSET commands by clearing the maildir, and by
silencing annoying asyncore error messages.

Extend the doctest runner so that it will run .txt files in any docs
subdirectory in the code tree.

Add plugable keys 'mailman.mta' and 'mailman.rules'.  The latter may have only
one setting while the former is extensible.

There are lots of doctests which should give all the gory details.

Mailman/Post.py -> Mailman/inject.py and the command line usage of this module
is removed.

SQLALCHEMY_ECHO, which was unused, is removed.

Backport the ability to specify additional footer interpolation variables by
the message metadata 'decoration-data' key.

can_acknowledge() defines whether a message can be responded to by the email
robot.

Simplify the implementation of _reset() based on Storm fixes.  Be able to
handle lists in Storm values.

Do some reorganization.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 1998-2007 by the Free Software Foundation, Inc.
 
1
# Copyright (C) 1998-2008 by the Free Software Foundation, Inc.
2
2
#
3
3
# This program is free software; you can redistribute it and/or
4
4
# modify it under the terms of the GNU General Public License
12
12
#
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.
16
 
 
17
 
"""Incoming queue runner."""
18
 
 
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:
21
 
#
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
27
 
# mylist         -> post
28
 
# mylist-request -> request
29
 
#
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
36
 
# list owners.
37
 
#
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.
41
 
#
42
 
# So delivery flow of messages look like this:
43
 
#
44
 
# joerandom ---> mylist ---> list members
45
 
#    |                           |
46
 
#    |                           |[bounces]
47
 
#    |        mylist-bounces <---+ <-------------------------------+
48
 
#    |              |                                              |
49
 
#    |              +--->[internal bounce processing]              |
50
 
#    |              ^                |                             |
51
 
#    |              |                |    [bounce found]           |
52
 
#    |         [bounces *]           +--->[register and discard]   |
53
 
#    |              |                |                      |      |
54
 
#    |              |                |                      |[*]   |
55
 
#    |        [list owners]          |[no bounce found]     |      |
56
 
#    |              ^                |                      |      |
57
 
#    |              |                |                      |      |
58
 
#    +-------> mylist-owner <--------+                      |      |
59
 
#    |                                                      |      |
60
 
#    |           data/owner-bounces.mbox <--[site list] <---+      |
61
 
#    |                                                             |
62
 
#    +-------> mylist-join--+                                      |
63
 
#    |                      |                                      |
64
 
#    +------> mylist-leave--+                                      |
65
 
#    |                      |                                      |
66
 
#    |                      v                                      |
67
 
#    +-------> mylist-request                                      |
68
 
#    |              |                                              |
69
 
#    |              +---> [command processor]                      |
70
 
#    |                            |                                |
71
 
#    +-----> mylist-confirm ----> +---> joerandom                  |
72
 
#                                           |                      |
73
 
#                                           |[bounces]             |
74
 
#                                           +----------------------+
75
 
#
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.
80
 
#
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).
87
 
#
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.
91
 
#
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,
 
16
# USA.
 
17
 
 
18
"""Incoming queue runner.
 
19
 
 
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.
 
23
 
 
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
 
26
immediately.
 
27
"""
95
28
 
96
29
 
97
30
 
98
 
import os
99
 
import sys
100
 
import logging
101
 
 
102
 
from cStringIO import StringIO
103
 
 
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
107
34
 
108
 
log     = logging.getLogger('mailman.error')
109
 
vlog    = logging.getLogger('mailman.vette')
110
 
 
111
35
 
112
36
 
113
37
class IncomingRunner(Runner):
115
39
 
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.
122
 
        #
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)
130
 
        if not more:
131
 
            del msgdata['pipeline']
132
 
        config.db.commit()
133
 
        return more
134
 
 
135
 
    # Overridable
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))[:]
142
 
 
143
 
    def _dopipeline(self, mlist, msg, msgdata, pipeline):
144
 
        while pipeline:
145
 
            handler = pipeline.pop(0)
146
 
            modname = 'Mailman.Handlers.' + handler
147
 
            __import__(modname)
148
 
            try:
149
 
                pid = os.getpid()
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)
154
 
                    os._exit(1)
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'))
159
 
                return 0
160
 
            except Errors.HoldMessage:
161
 
                # Let the approval process take it from here.  The message no
162
 
                # longer needs to be queued.
163
 
                return 0
164
 
            except Errors.RejectMessage, e:
165
 
                mlist.bounce_message(msg, e)
166
 
                return 0
167
 
            except:
168
 
                # Push this pipeline module back on the stack, then re-raise
169
 
                # the exception.
170
 
                pipeline.insert(0, handler)
171
 
                raise
172
 
        # We've successfully completed handling of this message
173
 
        return 0
 
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.
 
46
        return False