diff options
Diffstat (limited to 'src/mailman_pgp/mta/deliver.py')
| -rw-r--r-- | src/mailman_pgp/mta/deliver.py | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/src/mailman_pgp/mta/deliver.py b/src/mailman_pgp/mta/deliver.py new file mode 100644 index 0000000..050a740 --- /dev/null +++ b/src/mailman_pgp/mta/deliver.py @@ -0,0 +1,136 @@ +# Copyright (C) 2017 Jan Jancar +# +# This file is a part of the Mailman PGP plugin. +# +# 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 3 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, see <http://www.gnu.org/licenses/>. + +"""""" +import logging +import time + +from mailman.config import config +from mailman.interfaces.mailinglist import Personalization +from mailman.interfaces.mta import SomeRecipientsFailed +from mailman.mta.bulk import BulkDelivery +from mailman.mta.deliver import Deliver +from mailman.utilities.string import expand +from public import public + +from mailman_pgp.model.list import PGPMailingList +from mailman_pgp.mta.bulk import PGPBulkDelivery +from mailman_pgp.mta.personalized import PGPPersonalizedDelivery + +COMMA = ',' +log = logging.getLogger('mailman.smtp') + + +@public +def deliver(mlist, msg, msgdata): + """Deliver a message to the outgoing mail server.""" + # If there are no recipients, there's nothing to do. + recipients = msgdata.get('recipients') + if not recipients: + # Could be None, could be an empty sequence. + return + # Which delivery agent should we use? Several situations can cause us to + # use individual delivery. If not specified, use bulk delivery. See the + # to-outgoing handler for when the 'verp' key is set in the metadata. + personalized_agent = Deliver + bulk_agent = BulkDelivery + + pgp_list = PGPMailingList.for_list(mlist) + if pgp_list: + personalized_agent = PGPPersonalizedDelivery + bulk_agent = PGPBulkDelivery + + if msgdata.get('verp', False): + agent = personalized_agent() + elif mlist.personalize != Personalization.none: + agent = personalized_agent() + else: + agent = bulk_agent(int(config.mta.max_recipients)) + log.debug('Using agent: %s', agent) + # Keep track of the original recipients and the original sender for + # logging purposes. + original_recipients = msgdata['recipients'] + original_sender = msgdata.get('original-sender', msg.sender) + # Let the agent attempt to deliver to the recipients. Record all failures + # for re-delivery later. + t0 = time.time() + refused = agent.deliver(mlist, msg, msgdata) + t1 = time.time() + # Log this posting. + size = getattr(msg, 'original_size', msgdata.get('original_size')) + if size is None: + size = len(msg.as_string()) + substitutions = dict( + msgid=msg.get('message-id', 'n/a'), # noqa: E221, E251 + listname=mlist.fqdn_listname, # noqa: E221, E251 + sender=original_sender, # noqa: E221, E251 + recip=len(original_recipients), # noqa: E221, E251 + size=size, # noqa: E221, E251 + time=t1 - t0, # noqa: E221, E251 + refused=len(refused), # noqa: E221, E251 + smtpcode='n/a', # noqa: E221, E251 + smtpmsg='n/a', # noqa: E221, E251 + ) + template = config.logging.smtp.every + if template.lower() != 'no': + log.info('%s', expand(template, mlist, substitutions)) + if refused: + template = config.logging.smtp.refused + if template.lower() != 'no': + log.info('%s', expand(template, mlist, substitutions)) + else: + # Log the successful post, but if it was not destined to the mailing + # list (e.g. to the owner or admin), print the actual recipients + # instead of just the number. + if not msgdata.get('tolist', False): + recips = msg.get_all('to', []) + recips.extend(msg.get_all('cc', [])) + substitutions['recips'] = COMMA.join(recips) + template = config.logging.smtp.success + if template.lower() != 'no': + log.info('%s', expand(template, mlist, substitutions)) + # Process any failed deliveries. + temporary_failures = [] + permanent_failures = [] + for recipient, (code, smtp_message) in refused.items(): + # RFC 5321, $4.5.3.1.10 says: + # + # RFC 821 [1] incorrectly listed the error where an SMTP server + # exhausts its implementation limit on the number of RCPT commands + # ("too many recipients") as having reply code 552. The correct + # reply code for this condition is 452. Clients SHOULD treat a 552 + # code in this case as a temporary, rather than permanent, failure + # so the logic below works. + # + if code >= 500 and code != 552: + # A permanent failure + permanent_failures.append(recipient) + else: + # Deal with persistent transient failures by queuing them up for + # future delivery. TBD: this could generate lots of log entries! + temporary_failures.append(recipient) + template = config.logging.smtp.failure + if template.lower() != 'no': + substitutions.update( + recip=recipient, # noqa: E221, E251 + smtpcode=code, # noqa: E221, E251 + smtpmsg=smtp_message # noqa: E221, E251 + ) + log.info('%s', expand(template, mlist, substitutions)) + # Return the results + if temporary_failures or permanent_failures: + raise SomeRecipientsFailed(temporary_failures, permanent_failures) |
