summaryrefslogtreecommitdiff
path: root/mailman/tests/smtplistener.py
blob: 1dc11e3e0dea4e8768355c84eddeafd263908b8a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# Copyright (C) 2007-2008 by the Free Software Foundation, Inc.
#
# 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 2
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
# USA.

"""A test SMTP listener."""

import smtpd
import logging
import asyncore

from email import message_from_string


COMMASPACE = ', '
log = logging.getLogger('mailman.debug')



class Channel(smtpd.SMTPChannel):
    """A channel that can reset the mailbox."""

    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_EXIT(self, arg):
        """Respond to a new command EXIT by exiting the server."""
        self.push('250 Ok')
        self._server.stop()

    def send(self, data):
        """Silence the bloody asynchat/asyncore broken pipe errors!"""
        try:
            return smtpd.SMTPChannel.send(self, data)
        except socket.error:
            # Nothing here can affect the outcome, and these messages are just
            # plain annoying!  So ignore them.
            pass



class Server(smtpd.SMTPServer):
    """An SMTP server that stores messages to a mailbox."""

    def __init__(self, localaddr, queue):
        smtpd.SMTPServer.__init__(self, localaddr, None)
        self._queue = queue

    def handle_accept(self):
        """Handle connections by creating our own Channel object."""
        conn, addr = self.accept()
        log.info('accepted: %s', addr)
        Channel(self, conn, addr)

    def process_message(self, peer, mailfrom, rcpttos, data):
        """Process a message by adding it to the mailbox."""
        message = message_from_string(data)
        message['X-Peer'] = '%s:%s' % peer
        message['X-MailFrom'] = mailfrom
        message['X-RcptTo'] = COMMASPACE.join(rcpttos)
        log.info('processed message: %s', message.get('message-id', 'n/a'))
        self._queue.put(message)

    def start(self):
        """Start the asyncore loop."""
        asyncore.loop()

    def stop(self):
        """Stop the asyncore loop."""
        asyncore.socket_map.clear()
        asyncore.close_all()
        self.close()