diff --git a/Lib/smtpd.py b/Lib/smtpd.py index 8103ca9af0d7b3..7f1e626c758748 100755 --- a/Lib/smtpd.py +++ b/Lib/smtpd.py @@ -738,16 +738,17 @@ def __init__(self, *args, **kwargs): raise ValueError("PureProxy does not support SMTPUTF8.") super(PureProxy, self).__init__(*args, **kwargs) - def process_message(self, peer, mailfrom, rcpttos, data): - lines = data.split('\n') + def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): + lines = data.split(b'\n') # Look for the last header i = 0 for line in lines: if not line: break i += 1 - lines.insert(i, 'X-Peer: %s' % peer[0]) - data = NEWLINE.join(lines) + peer_address = peer[0].encode('ascii') + lines.insert(i, b'X-Peer: %s' % peer_address) + data = b'\n'.join(lines) refused = self._deliver(mailfrom, rcpttos, data) # TBD: what to do with refused addresses? print('we got some refusals:', refused, file=DEBUGSTREAM) @@ -783,7 +784,7 @@ def __init__(self, *args, **kwargs): raise ValueError("MailmanProxy does not support SMTPUTF8.") super(PureProxy, self).__init__(*args, **kwargs) - def process_message(self, peer, mailfrom, rcpttos, data): + def process_message(self, peer, mailfrom, rcpttos, data, **kwargs): from io import StringIO from Mailman import Utils from Mailman import Message @@ -826,7 +827,7 @@ def process_message(self, peer, mailfrom, rcpttos, data): print('we got refusals:', refused, file=DEBUGSTREAM) # Now deliver directly to the list commands mlists = {} - s = StringIO(data) + s = StringIO(data.decode()) msg = Message.Message(s) # These headers are required for the proper execution of Mailman. All # MTAs in existence seem to add these if the original message doesn't diff --git a/Lib/test/test_smtpd.py b/Lib/test/test_smtpd.py index a9f7d5a3b8bc19..4c6fc605a8b1c3 100644 --- a/Lib/test/test_smtpd.py +++ b/Lib/test/test_smtpd.py @@ -5,6 +5,8 @@ import io import smtpd import asyncore +import smtplib +from unittest.mock import MagicMock, patch class DummyServer(smtpd.SMTPServer): @@ -160,6 +162,84 @@ def tearDown(self): asyncore.socket = smtpd.socket = socket +class PureProxyServerTest(unittest.TestCase): + + def setUp(self): + smtpd.socket = asyncore.socket = mock_socket + + @patch('smtplib.SMTP') + def test_proxy_successful(self, mock_smtp): + fake_smtp = MagicMock() + fake_smtp.sendmail = MagicMock(return_value={}) + mock_smtp.return_value = fake_smtp + server = smtpd.PureProxy((support.HOST, 0), ('b', 0)) + conn, addr = server.accept() + channel = smtpd.SMTPChannel(server, conn, addr) + + def write_line(line): + channel.socket.queue_recv(line) + channel.handle_read() + + write_line(b'EHLO example') + write_line(b'MAIL From:eggs@example') + write_line(b'RCPT To:spam@example') + write_line(b'DATA') + write_line(b'From: test\n\nhello\n') + write_line(b'.') + + mock_smtp.assert_called_once_with() + fake_smtp.connect.assert_called_once_with('b', 0) + fake_smtp.sendmail.assert_called_once_with( + 'eggs@example', + ['spam@example'], + b'From: test\nX-Peer: peer-address\n\nhello\n' + ) + fake_smtp.quit.assert_called_once_with() + + @patch('smtplib.SMTP') + def test_deliver_refused(self, mock_smtp): + fake_smtp = MagicMock() + exc = smtplib.SMTPRecipientsRefused({'error@example': (1, 'testing')}) + fake_smtp.sendmail = MagicMock(side_effect=exc) + mock_smtp.return_value = fake_smtp + + server = smtpd.PureProxy((support.HOST, 0), ('b', 0)) + refused = server._deliver( + 'eggs@example', + ['spam@example'], + b'From: test\nX-Peer: peer-address\n\nhello\n' + ) + self.assertEqual(refused, {'error@example': (1, 'testing')}) + fake_smtp.connect.assert_called_once_with('b', 0) + fake_smtp.quit.assert_called_once_with() + + @patch('smtplib.SMTP') + def test_deliver_error(self, mock_smtp): + fake_smtp = MagicMock() + exc = OSError() + exc.smtp_code = 42 + fake_smtp.sendmail = MagicMock(side_effect=exc) + mock_smtp.return_value = fake_smtp + + server = smtpd.PureProxy((support.HOST, 0), ('b', 0)) + refused = server._deliver( + 'eggs@example', + ['spam@example'], + b'From: test\nX-Peer: peer-address\n\nhello\n' + ) + self.assertEqual(refused, {'spam@example': (42, 'ignore')}) + fake_smtp.connect.assert_called_once_with('b', 0) + fake_smtp.quit.assert_called_once_with() + + def test_init_enable_SMTPUTF8(self): + self.assertRaises(ValueError, smtpd.PureProxy, + (support.HOST, 0), ('b', 0), enable_SMTPUTF8=True) + + def tearDown(self): + asyncore.close_all() + asyncore.socket = smtpd.socket = socket + + class TestFamilyDetection(unittest.TestCase): def setUp(self): smtpd.socket = asyncore.socket = mock_socket diff --git a/Misc/ACKS b/Misc/ACKS index 81b51f75199159..ceebc5c9e2e5f4 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -313,6 +313,7 @@ Benjamin Collar Jeffery Collins Robert Collins Paul Colomiets +Samuel Colvin Christophe Combelles Geremy Condra Denver Coneybeare diff --git a/Misc/NEWS.d/next/Library/2019-01-20-12-37-52.bpo-35788.Kj6705.rst b/Misc/NEWS.d/next/Library/2019-01-20-12-37-52.bpo-35788.Kj6705.rst new file mode 100644 index 00000000000000..e390a408f1c8fb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-01-20-12-37-52.bpo-35788.Kj6705.rst @@ -0,0 +1 @@ +Fix regressions in ``smtpd.PureProxy`` and ``smtpd.MailmanProxy``.