summaryrefslogtreecommitdiff
path: root/Mailman/Handlers/HandlerAPI.py
blob: aba0e1a09679b63ae0baf4471176a3a63f0f12d4 (plain)
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
# Copyright (C) 1998,1999,2000 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.

"""Contains all the common functionality for the msg handler API."""

import traceback
import time

from Mailman import mm_cfg
from Mailman import Errors
from Mailman.pythonlib.StringIO import StringIO



# Exception classes for this subsystem.
class HandlerError(Errors.MailmanError):
    """Base class for all handler errors."""

class MessageHeld(HandlerError):
    """Base class for all message-being-held short circuits."""
    def __str__(self):
        return self.__class__.__doc__

class DiscardMessage(HandlerError):
    """The message can be discarded with no further action"""

class SomeRecipientsFailed(HandlerError):
    """Delivery to some or all recipients failed"""



# All messages which are delivered to the entire list membership go through
# this pipeline of handler modules.
LIST_PIPELINE = ['SpamDetect',
                 'Approve',
                 'Replybot',
                 'Hold',
                 'Cleanse',
                 'CookHeaders',
                 'ToDigest',
                 'ToArchive',
                 'ToUsenet',
                 'CalcRecips',
                 'Decorate',
                 mm_cfg.DELIVERY_MODULE,
                 'AfterDelivery',
                 'Acknowledge',
                 ]



# Central mail delivery handler
def DeliverToList(mlist, msg, msgdata):
    pipeline = msgdata.get('pipeline', LIST_PIPELINE)
    while pipeline:
        modname = pipeline.pop(0)
        mod = __import__('Mailman.Handlers.' + modname)
        func = getattr(getattr(getattr(mod, 'Handlers'), modname), 'process')
        try:
            mlist.LogMsg('debug', 'starting ' + modname)
            func(mlist, msg, msgdata)
            mlist.LogMsg('debug', 'done with ' + modname)
        except DiscardMessage:
            # Throw the message away; we need do nothing else with it.
            return 0
        except MessageHeld:
            # Let the approval process take it from here.  The message no
            # longer needs to be queued.
            return 0
        except SomeRecipientsFailed:
            # The delivery module being used (SMTPDirect or Sendmail) failed
            # to deliver the message to one or all of the recipients.  Push
            # the delivery module back on the pipeline list and break.
            pipeline.insert(0, modname)
            # Consult and adjust some meager metrics that try to decide
            # whether it's worth continuing to attempt delivery of this
            # message.
            now = time.time()
            recips = msgdata['recips']
            last_recip_count = msgdata.get('last_recip_count', 0)
            deliver_until = msgdata.get('deliver_until', now)
            if len(recips) == last_recip_count:
                # We didn't make any progress.  How many times to we continue
                # to attempt delivery?  TBD: make this configurable.
                if now > deliver_until:
                    # throw this message away
                    return 0
            else:
                # Keep trying to delivery this for 3 days
                deliver_until = now + 60*60*24*3
            msgdata['last_recip_count'] = len(recips)
            msgdata['deliver_until'] = deliver_until
            break
        except Exception, e:
            # Some other exception occurred, which we definitely did not
            # expect, so set this message up for queuing.  This is mildly
            # offensive since we're doing the equivalent of a bare except,
            # which gobbles useful bug reporting.  Still, it's more important
            # that email not get lost, so we log the exception and the
            # traceback so that we have a hope of fixing this.  We may want to
            # email the site admin or (shudder) the Mailman maintainers.
            #
            # We stick the name of the failed module back into the front of
            # the pipeline list so that it can resume where it left off when
            # qrunner tries to redeliver it.
            pipeline.insert(0, modname)
            mlist.LogMsg('error', 'Delivery exception: %s' % e)
            s = StringIO()
            traceback.print_exc(file=s)
            mlist.LogMsg('error', s.getvalue())
            break
    msgdata['pipeline'] = pipeline
    return len(pipeline)



# For messages that qrunner tries to re-deliver using the pre 2.0beta3 qfiles
# data format.
def RedeliverMessage(mlist, msg):
    msgdata = {'pipeline': [mm_cfg.DELIVERY_MODULE]}
    return DeliverToList(mlist, msg, msgdata)



# for messages crafted internally by the Mailman system.  The msg object
# should have already calculated and set msg.recips.  TBD: can the mlist be
# None?
def DeliverToUser(mlist, msg):
    pipeline = ['Replybot',
                'CookHeaders',
                mm_cfg.DELIVERY_MODULE,
                ]
    msgdata = {'pipeline' : pipeline,
               'fasttrack': 1,
               'recips'   : msg.recips,
               }
    return DeliverToList(mlist, msg, msgdata)