diff options
| author | Barry Warsaw | 2011-05-29 12:45:19 -0400 |
|---|---|---|
| committer | Barry Warsaw | 2011-05-29 12:45:19 -0400 |
| commit | 521a179d309fac857fdbbe162d5db136c3ec3b1e (patch) | |
| tree | ec6e635e9c0f8a5bd655a254f9c346f1acb6dd8e /src/mailman/queue/lmtp.py | |
| parent | 0f760798fb2490a03041c42018afbd59749e6cbd (diff) | |
| download | mailman-521a179d309fac857fdbbe162d5db136c3ec3b1e.tar.gz mailman-521a179d309fac857fdbbe162d5db136c3ec3b1e.tar.zst mailman-521a179d309fac857fdbbe162d5db136c3ec3b1e.zip | |
Diffstat (limited to 'src/mailman/queue/lmtp.py')
| -rw-r--r-- | src/mailman/queue/lmtp.py | 236 |
1 files changed, 0 insertions, 236 deletions
diff --git a/src/mailman/queue/lmtp.py b/src/mailman/queue/lmtp.py deleted file mode 100644 index 9163a88e6..000000000 --- a/src/mailman/queue/lmtp.py +++ /dev/null @@ -1,236 +0,0 @@ -# Copyright (C) 2006-2011 by the Free Software Foundation, Inc. -# -# This file is part of GNU Mailman. -# -# GNU Mailman 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. -# -# GNU Mailman 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 -# GNU Mailman. If not, see <http://www.gnu.org/licenses/>. - -"""Mailman LMTP runner (server). - -Most mail servers can be configured to deliver local messages via 'LMTP'[1]. -This module is actually an LMTP server rather than a standard queue runner. - -The LMTP runner opens a local TCP port and waits for the mail server to -connect to it. The messages it receives over LMTP are very minimally parsed -for sanity and if they look okay, they are accepted and injected into -Mailman's incoming queue for normal processing. If they don't look good, or -are destined for a bogus sub-address, they are rejected right away, hopefully -so that the peer mail server can provide better diagnostics. - -[1] RFC 2033 Local Mail Transport Protocol - http://www.faqs.org/rfcs/rfc2033.html -""" - -import email -import smtpd -import logging -import asyncore - -from email.utils import parseaddr -from zope.component import getUtility - -from mailman.config import config -from mailman.database.transaction import txn -from mailman.email.message import Message -from mailman.interfaces.listmanager import IListManager -from mailman.queue import Runner - -elog = logging.getLogger('mailman.error') -qlog = logging.getLogger('mailman.qrunner') - - -# We only care about the listname and the sub-addresses as in listname@ or -# listname-request@. This maps user visible subaddress names (which may -# include aliases) to the internal canonical subaddress name. -SUBADDRESS_NAMES = dict( - admin='bounces', - bounces='bounces', - confirm='confirm', - join='join', - leave='leave', - owner='owner', - request='request', - subscribe='join', - unsubscribe='leave', - ) - -# This maps subaddress canonical name to the destination queue that handles -# messages sent to that subaddress. -SUBADDRESS_QUEUES = dict( - bounces='bounces', - confirm='command', - join='command', - leave='command', - owner='in', - request='command', - ) - -DASH = '-' -CRLF = '\r\n' -ERR_451 = '451 Requested action aborted: error in processing' -ERR_501 = '501 Message has defects' -ERR_502 = '502 Error: command HELO not implemented' -ERR_550 = '550 Requested action not taken: mailbox unavailable' - -# XXX Blech -smtpd.__version__ = 'Python LMTP queue runner 1.0' - - - -def split_recipient(address): - """Split an address into listname, subaddress and domain parts. - - For example: - - >>> split_recipient('mylist@example.com') - ('mylist', None, 'example.com') - - >>> split_recipient('mylist-request@example.com') - ('mylist', 'request', 'example.com') - - :param address: The destination address. - :return: A 3-tuple of the form (list-shortname, subaddress, domain). - subaddress may be None if this is the list's posting address. - """ - localpart, domain = address.split('@', 1) - localpart = localpart.split(config.mta.verp_delimiter, 1)[0] - parts = localpart.split(DASH) - if parts[-1] in SUBADDRESS_NAMES: - listname = DASH.join(parts[:-1]) - subaddress = parts[-1] - else: - listname = localpart - subaddress = None - return listname, subaddress, domain - - - -class Channel(smtpd.SMTPChannel): - """An LMTP channel.""" - - def __init__(self, server, conn, addr): - smtpd.SMTPChannel.__init__(self, server, conn, addr) - # Stash this here since the subclass uses private attributes. :( - self._server = server - - def smtp_LHLO(self, arg): - """The LMTP greeting, used instead of HELO/EHLO.""" - smtpd.SMTPChannel.smtp_HELO(self, arg) - - def smtp_HELO(self, arg): - """HELO is not a valid LMTP command.""" - self.push(ERR_502) - - - -class LMTPRunner(Runner, smtpd.SMTPServer): - # Only __init__ is called on startup. Asyncore is responsible for later - # connections from the MTA. slice and numslices are ignored and are - # necessary only to satisfy the API. - def __init__(self, slice=None, numslices=1): - localaddr = config.mta.lmtp_host, int(config.mta.lmtp_port) - # Do not call Runner's constructor because there's no QDIR to create - qlog.debug('LMTP server listening on %s:%s', - localaddr[0], localaddr[1]) - smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None) - - def handle_accept(self): - conn, addr = self.accept() - Channel(self, conn, addr) - qlog.debug('LMTP accept from %s', addr) - - @txn - def process_message(self, peer, mailfrom, rcpttos, data): - try: - # Refresh the list of list names every time we process a message - # since the set of mailing lists could have changed. - listnames = set(getUtility(IListManager).names) - # Parse the message data. If there are any defects in the - # message, reject it right away; it's probably spam. - msg = email.message_from_string(data, Message) - msg.original_size = len(data) - if msg.defects: - return ERR_501 - msg['X-MailFrom'] = mailfrom - message_id = msg['message-id'] - except Exception: - elog.exception('LMTP message parsing') - config.db.abort() - return CRLF.join(ERR_451 for to in rcpttos) - # RFC 2033 requires us to return a status code for every recipient. - status = [] - # Now for each address in the recipients, parse the address to first - # see if it's destined for a valid mailing list. If so, then queue - # the message to the appropriate place and record a 250 status for - # that recipient. If not, record a failure status for that recipient. - for to in rcpttos: - try: - to = parseaddr(to)[1].lower() - listname, subaddress, domain = split_recipient(to) - qlog.debug('%s to: %s, list: %s, sub: %s, dom: %s', - message_id, to, listname, subaddress, domain) - listname += '@' + domain - if listname not in listnames: - status.append(ERR_550) - continue - # The recipient is a valid mailing list. Find the subaddress - # if there is one, and set things up to enqueue to the proper - # queue runner. - queue = None - msgdata = dict(listname=listname, - original_size=msg.original_size) - canonical_subaddress = SUBADDRESS_NAMES.get(subaddress) - queue = SUBADDRESS_QUEUES.get(canonical_subaddress) - if subaddress is None: - # The message is destined for the mailing list. - msgdata['to_list'] = True - queue = 'in' - elif canonical_subaddress is None: - # The subaddress was bogus. - elog.error('%s unknown sub-address: %s', - message_id, subaddress) - status.append(ERR_550) - continue - else: - # A valid subaddress. - msgdata['subaddress'] = canonical_subaddress - if canonical_subaddress == 'owner': - msgdata.update(dict( - to_owner=True, - envsender=config.mailman.site_owner, - )) - queue = 'in' - # If we found a valid destination, enqueue the message and add - # a success status for this recipient. - if queue is not None: - config.switchboards[queue].enqueue(msg, msgdata) - qlog.debug('%s subaddress: %s, queue: %s', - message_id, canonical_subaddress, queue) - status.append('250 Ok') - except Exception: - elog.exception('Queue detection: %s', msg['message-id']) - config.db.abort() - status.append(ERR_550) - # All done; returning this big status string should give the expected - # response to the LMTP client. - return CRLF.join(status) - - def run(self): - """See `IRunner`.""" - asyncore.loop() - - def stop(self): - """See `IRunner`.""" - asyncore.socket_map.clear() - asyncore.close_all() - self.close() |
