Clean up the mta directory.
[mailman.git] / src / mailman / runners / outgoing.py
blob98c484e3fce745ed0254b07c5518b6a319f78c58
1 # Copyright (C) 2000-2016 by the Free Software Foundation, Inc.
3 # This file is part of GNU Mailman.
5 # GNU Mailman is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free
7 # Software Foundation, either version 3 of the License, or (at your option)
8 # any later version.
10 # GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
11 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 # more details.
15 # You should have received a copy of the GNU General Public License along with
16 # GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
18 """Outgoing runner."""
20 __all__ = [
21 'OutgoingRunner',
25 import socket
26 import logging
28 from datetime import datetime
29 from lazr.config import as_boolean, as_timedelta
30 from mailman.config import config
31 from mailman.core.runner import Runner
32 from mailman.interfaces.bounce import BounceContext, IBounceProcessor
33 from mailman.interfaces.mailinglist import Personalization
34 from mailman.interfaces.mta import SomeRecipientsFailed
35 from mailman.interfaces.pending import IPendings
36 from mailman.interfaces.subscriptions import ISubscriptionService
37 from mailman.utilities.datetime import now
38 from mailman.utilities.modules import find_name
39 from uuid import UUID
40 from zope.component import getUtility
43 # This controls how often _do_periodic() will try to deal with deferred
44 # permanent failures. It is a count of calls to _do_periodic()
45 DEAL_WITH_PERMFAILURES_EVERY = 10
47 log = logging.getLogger('mailman.error')
48 smtp_log = logging.getLogger('mailman.smtp')
49 debug_log = logging.getLogger('mailman.debug')
53 class OutgoingRunner(Runner):
54 """The outgoing runner."""
56 def __init__(self, slice=None, numslices=1):
57 super().__init__(slice, numslices)
58 # We look this function up only at startup time.
59 self._func = find_name(config.mta.outgoing)
60 # This prevents smtp server connection problems from filling up the
61 # error log. It gets reset if the message was successfully sent, and
62 # set if there was a socket.error.
63 self._logged = False
64 self._retryq = config.switchboards['retry']
66 def _dispose(self, mlist, msg, msgdata):
67 # See if we should retry delivery of this message again.
68 deliver_after = msgdata.get('deliver_after', datetime.fromtimestamp(0))
69 if now() < deliver_after:
70 return True
71 # Calculate whether we should VERP this message or not. The results of
72 # this set the 'verp' key in the message metadata.
73 interval = int(config.mta.verp_delivery_interval)
74 if 'verp' in msgdata:
75 # Honor existing settings.
76 pass
77 # If personalization is enabled for this list and we've configured
78 # Mailman to always VERP personalized deliveries, then yes we VERP it.
79 # Also, if personalization is /not/ enabled, but
80 # verp_delivery_interval is set (and we've hit this interval), then
81 # again, this message should be VERP'd. Otherwise, no.
82 elif mlist.personalize != Personalization.none:
83 if as_boolean(config.mta.verp_personalized_deliveries):
84 msgdata['verp'] = True
85 elif interval == 0:
86 # Never VERP.
87 msgdata['verp'] = False
88 elif interval == 1:
89 # VERP every time.
90 msgdata['verp'] = True
91 else:
92 # VERP every 'interval' number of times.
93 msgdata['verp'] = (mlist.post_id % interval == 0)
94 try:
95 debug_log.debug('[outgoing] {0}: {1}'.format(
96 self._func, msg.get('message-id', 'n/a')))
97 self._func(mlist, msg, msgdata)
98 self._logged = False
99 except socket.error:
100 # There was a problem connecting to the SMTP server. Log this
101 # once, but crank up our sleep time so we don't fill the error
102 # log.
103 port = int(config.mta.smtp_port)
104 if port == 0:
105 port = 'smtp' # Log this just once.
106 if not self._logged:
107 log.error('Cannot connect to SMTP server %s on port %s',
108 config.mta.smtp_host, port)
109 self._logged = True
110 return True
111 except SomeRecipientsFailed as error:
112 processor = getUtility(IBounceProcessor)
113 # BAW: msg is the original message that failed delivery, not a
114 # bounce message. This may be confusing if this is what's sent to
115 # the user in the probe message. Maybe we should craft a
116 # bounce-like message containing information about the permanent
117 # SMTP failure?
118 if 'probe_token' in msgdata:
119 # This is a failure of our local MTA to deliver to a probe
120 # message recipient. Register the bounce event for permanent
121 # failures. Start by grabbing and confirming (i.e. removing)
122 # the pendable record associated with this bounce token,
123 # regardless of what address was actually failing.
124 if len(error.permanent_failures) > 0:
125 pended = getUtility(IPendings).confirm(
126 msgdata['probe_token'])
127 # It's possible the token has been confirmed out of the
128 # database. Just ignore that.
129 if pended is not None:
130 # The UUID had to be pended as a unicode.
131 member = getUtility(ISubscriptionService).get_member(
132 UUID(hex=pended['member_id']))
133 processor.register(
134 mlist, member.address.email, msg,
135 BounceContext.probe)
136 else:
137 # Delivery failed at SMTP time for some or all of the
138 # recipients. Permanent failures are registered as bounces,
139 # but temporary failures are retried for later.
140 for email in error.permanent_failures:
141 processor.register(mlist, email, msg, BounceContext.normal)
142 # Move temporary failures to the qfiles/retry queue which will
143 # occasionally move them back here for another shot at
144 # delivery.
145 if error.temporary_failures:
146 current_time = now()
147 recipients = error.temporary_failures
148 last_recip_count = msgdata.get('last_recip_count', 0)
149 deliver_until = msgdata.get('deliver_until', current_time)
150 if len(recipients) == last_recip_count:
151 # We didn't make any progress. If we've exceeded the
152 # configured retry period, log this failure and
153 # discard the message.
154 if current_time > deliver_until:
155 smtp_log.error('Discarding message with '
156 'persistent temporary failures: '
157 '{0}'.format(msg['message-id']))
158 return False
159 else:
160 # We made some progress, so keep trying to delivery
161 # this message for a while longer.
162 deliver_until = current_time + as_timedelta(
163 config.mta.delivery_retry_period)
164 msgdata['last_recip_count'] = len(recipients)
165 msgdata['deliver_until'] = deliver_until
166 msgdata['recipients'] = recipients
167 self._retryq.enqueue(msg, msgdata)
168 # We've successfully completed handling of this message.
169 return False