1 # Copyright (C) 2009-2023 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 <https://www.gnu.org/licenses/>.
18 """Base delivery class."""
25 from lazr
.config
import as_boolean
26 from mailman
.config
import config
27 from mailman
.interfaces
.mta
import IMailTransportAgentDelivery
28 from mailman
.mta
.connection
import as_SecureMode
, Connection
29 from public
import public
30 from zope
.interface
import implementer
33 log
= logging
.getLogger('mailman.smtp')
37 @implementer(IMailTransportAgentDelivery
)
39 """Base delivery class."""
42 """Create a basic deliverer."""
43 self
._connection
= Connection(
44 config
.mta
.smtp_host
, int(config
.mta
.smtp_port
),
45 int(config
.mta
.max_sessions_per_connection
),
46 config
.mta
.smtp_user
if config
.mta
.smtp_user
else None,
47 config
.mta
.smtp_pass
if config
.mta
.smtp_pass
else None,
48 as_SecureMode(config
.mta
.smtp_secure_mode
),
49 as_boolean(config
.mta
.smtp_verify_cert
),
50 as_boolean(config
.mta
.smtp_verify_hostname
),
53 def _deliver_to_recipients(self
, mlist
, msg
, msgdata
, recipients
):
54 """Low-level delivery to a set of recipients.
56 :param mlist: The mailing list being delivered to.
57 :type mlist: `IMailingList`
58 :param msg: The original message being delivered.
60 :param msgdata: Additional message metadata for this delivery.
61 :type msgdata: dictionary
62 :param recipients: The recipients of this message.
63 :type recipients: sequence
64 :return: delivery failures as defined by `smtplib.SMTP.sendmail`
67 # Do the actual sending.
68 sender
= self
._get
_sender
(mlist
, msg
, msgdata
)
69 message_id
= msg
['message-id']
70 # Since the recipients can be a set or a list, sort the recipients by
71 # email address for predictability and testability.
73 refused
= self
._connection
.sendmail(
74 sender
, sorted(recipients
), msg
)
75 except smtplib
.SMTPRecipientsRefused
as error
:
76 log
.error('%s recipients refused: %s', message_id
, error
)
77 refused
= error
.recipients
78 except smtplib
.SMTPResponseException
as error
:
79 log
.error('%s response exception: %s', message_id
, error
)
81 # recipient -> (code, error)
82 (recipient
, (error
.smtp_code
, error
.smtp_error
))
83 for recipient
in recipients
)
84 except (socket
.error
, IOError, smtplib
.SMTPException
) as error
:
85 # MTA not responding, or other socket problems, or any other
86 # kind of SMTPException. In that case, nothing got delivered,
87 # so treat this as a temporary failure. We use error code 444
88 # for this (temporary, unspecified failure, cf RFC 5321).
89 log
.error('%s low level smtp error: %s', message_id
, error
)
92 # recipient -> (code, error)
93 (recipient
, (444, error
))
94 for recipient
in recipients
)
97 def _get_sender(self
, mlist
, msg
, msgdata
):
98 """Return the envelope sender to use.
100 The message metadata can override the calculation of the sender, but
101 otherwise it falls to the list's -bounces robot. If this message is
102 not intended for any specific mailing list, the site owner's address
105 :param mlist: The mailing list being delivered to.
106 :type mlist: `IMailingList`
107 :param msg: The original message being delivered.
109 :param msgdata: Additional message metadata for this delivery.
110 :type msgdata: dictionary
111 :return: The envelope sender.
114 sender
= msgdata
.get('sender')
116 return (config
.mailman
.site_owner
118 else mlist
.bounces_address
)
123 class IndividualDelivery(BaseDelivery
):
124 """Deliver a unique individual message to each recipient.
126 This is a framework delivery mechanism. By using mixins, registration,
127 and subclassing you can customize this delivery class to do any
128 combination of VERP, full personalization, individualized header/footer
129 decoration and even full mail merging.
131 The core concept here is that for each recipient, the deliver() method
132 iterates over the list of registered callbacks, each of which have a
133 chance to modify the message before final delivery.
137 """See `BaseDelivery`."""
141 def deliver(self
, mlist
, msg
, msgdata
):
142 """See `IMailTransportAgentDelivery`.
144 Craft a unique message for every recipient. Encode the recipient's
145 delivery address in the return envelope so there can be no ambiguity
146 in bounce processing.
149 recipients
= msgdata
.get('recipients', set())
150 for recipient
in recipients
:
151 log
.debug('IndividualDelivery to: %s', recipient
)
152 # Make a copy of the original messages and operator on it, since
153 # we're going to munge it repeatedly for each recipient.
154 message_copy
= copy
.deepcopy(msg
)
155 msgdata_copy
= msgdata
.copy()
156 # Squirrel the current recipient away in the message metadata.
157 # That way the subclass's _get_sender() override can encode the
158 # recipient address in the sender, e.g. for VERP.
159 msgdata_copy
['recipient'] = recipient
160 # See if the recipient is a member of the mailing list, and if so,
161 # squirrel this information away for use by other modules, such as
162 # the header/footer decorator. XXX 2012-03-05 this is probably
163 # highly inefficient on the database.
164 member
= mlist
.members
.get_member(recipient
)
165 msgdata_copy
['member'] = member
166 for callback
in self
.callbacks
:
167 callback(mlist
, message_copy
, msgdata_copy
)
168 status
= self
._deliver
_to
_recipients
(
169 mlist
, message_copy
, msgdata_copy
, [recipient
])
170 refused
.update(status
)