smtp_direct.py is dead and gone.
[mailman.git] / src / mailman / queue / outgoing.py
blobb1adbe4113ae85877a506f980f416cbef9103676
1 # Copyright (C) 2000-2009 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 queue runner."""
20 import os
21 import sys
22 import socket
23 import logging
25 from datetime import datetime
26 from lazr.config import as_timedelta
28 from mailman.config import config
29 from mailman.interfaces.mta import SomeRecipientsFailed
30 from mailman.queue import Runner
31 from mailman.queue.bounce import BounceMixin
32 from mailman.utilities.modules import find_name
35 # This controls how often _do_periodic() will try to deal with deferred
36 # permanent failures. It is a count of calls to _do_periodic()
37 DEAL_WITH_PERMFAILURES_EVERY = 10
39 log = logging.getLogger('mailman.error')
43 class OutgoingRunner(Runner, BounceMixin):
44 """The outgoing queue runner."""
46 def __init__(self, slice=None, numslices=1):
47 Runner.__init__(self, slice, numslices)
48 BounceMixin.__init__(self)
49 # We look this function up only at startup time.
50 self._func = find_name(config.mta.outgoing)
51 # This prevents smtp server connection problems from filling up the
52 # error log. It gets reset if the message was successfully sent, and
53 # set if there was a socket.error.
54 self._logged = False
55 self._retryq = config.switchboards['retry']
57 def _dispose(self, mlist, msg, msgdata):
58 # See if we should retry delivery of this message again.
59 deliver_after = msgdata.get('deliver_after', datetime.fromtimestamp(0))
60 if datetime.now() < deliver_after:
61 return True
62 # Make sure we have the most up-to-date state
63 try:
64 pid = os.getpid()
65 self._func(mlist, msg, msgdata)
66 # Failsafe -- a child may have leaked through.
67 if pid <> os.getpid():
68 log.error('child process leaked thru: %s', pid)
69 os._exit(1)
70 self._logged = False
71 except socket.error:
72 # There was a problem connecting to the SMTP server. Log this
73 # once, but crank up our sleep time so we don't fill the error
74 # log.
75 port = int(config.mta.port)
76 if port == 0:
77 port = 'smtp'
78 # Log this just once.
79 if not self._logged:
80 log.error('Cannot connect to SMTP server %s on port %s',
81 config.mta.host, port)
82 self._logged = True
83 return True
84 except SomeRecipientsFailed as error:
85 # Handle local rejects of probe messages differently.
86 if msgdata.get('probe_token') and error.permanent_failures:
87 self._probe_bounce(mlist, msgdata['probe_token'])
88 else:
89 # Delivery failed at SMTP time for some or all of the
90 # recipients. Permanent failures are registered as bounces,
91 # but temporary failures are retried for later.
93 # BAW: msg is going to be the original message that failed
94 # delivery, not a bounce message. This may be confusing if
95 # this is what's sent to the user in the probe message. Maybe
96 # we should craft a bounce-like message containing information
97 # about the permanent SMTP failure?
98 if error.permanent_failures:
99 self._queue_bounces(
100 mlist.fqdn_listname, error.permanent_failures, msg)
101 # Move temporary failures to the qfiles/retry queue which will
102 # occasionally move them back here for another shot at
103 # delivery.
104 if error.temporary_failures:
105 now = datetime.now()
106 recips = error.temporary_failures
107 last_recip_count = msgdata.get('last_recip_count', 0)
108 deliver_until = msgdata.get('deliver_until', now)
109 if len(recips) == last_recip_count:
110 # We didn't make any progress, so don't attempt
111 # delivery any longer. BAW: is this the best
112 # disposition?
113 if now > deliver_until:
114 return False
115 else:
116 # Keep trying to delivery this message for a while
117 deliver_until = now + as_timedelta(
118 config.mta.delivery_retry_period)
119 msgdata['last_recip_count'] = len(recips)
120 msgdata['deliver_until'] = deliver_until
121 msgdata['recipients'] = recips
122 self._retryq.enqueue(msg, msgdata)
123 # We've successfully completed handling of this message
124 return False
126 _do_periodic = BounceMixin._do_periodic
128 def _clean_up(self):
129 BounceMixin._clean_up(self)
130 Runner._clean_up(self)