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)
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
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."""
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
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.
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
:
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
)
75 # Honor existing settings.
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
87 msgdata
['verp'] = False
90 msgdata
['verp'] = True
92 # VERP every 'interval' number of times.
93 msgdata
['verp'] = (mlist
.post_id
% interval
== 0)
95 debug_log
.debug('[outgoing] {0}: {1}'.format(
96 self
._func
, msg
.get('message-id', 'n/a')))
97 self
._func
(mlist
, msg
, msgdata
)
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
103 port
= int(config
.mta
.smtp_port
)
105 port
= 'smtp' # Log this just once.
107 log
.error('Cannot connect to SMTP server %s on port %s',
108 config
.mta
.smtp_host
, port
)
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
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']))
134 mlist
, member
.address
.email
, msg
,
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
145 if error
.temporary_failures
:
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']))
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.