blob: d1ffb368a4f6f6c4fd01e681a7b497ec59eaf7cb [file] [log] [blame]
Guido van Rossum806c2462007-08-06 23:33:07 +00001import asyncore
R David Murrayb0deeb42015-11-08 01:03:52 -05002import base64
R. David Murray7dff9e02010-11-08 17:15:13 +00003import email.mime.text
R David Murray83084442015-05-17 19:27:22 -04004from email.message import EmailMessage
Barry Warsawc5ea7542015-07-09 10:39:55 -04005from email.base64mime import body_encode as encode_base64
Guido van Rossum04110fb2007-08-24 16:32:05 +00006import email.utils
Christian Heimesc64a1a62019-09-25 16:30:20 +02007import hashlib
R David Murrayb0deeb42015-11-08 01:03:52 -05008import hmac
Guido van Rossumd8faa362007-04-27 19:54:29 +00009import socket
Guido van Rossum806c2462007-08-06 23:33:07 +000010import smtpd
Guido van Rossumd8faa362007-04-27 19:54:29 +000011import smtplib
Guido van Rossum806c2462007-08-06 23:33:07 +000012import io
R. David Murray7dff9e02010-11-08 17:15:13 +000013import re
Guido van Rossum806c2462007-08-06 23:33:07 +000014import sys
Guido van Rossumd8faa362007-04-27 19:54:29 +000015import time
Guido van Rossum806c2462007-08-06 23:33:07 +000016import select
Ross Lagerwall86407432012-03-29 18:08:48 +020017import errno
R David Murray83084442015-05-17 19:27:22 -040018import textwrap
Antoine Pitroua6a4dc82017-09-07 18:56:24 +020019import threading
Guido van Rossumd8faa362007-04-27 19:54:29 +000020
Victor Stinner45df8202010-04-28 22:31:17 +000021import unittest
Richard Jones64b02de2010-08-03 06:39:33 +000022from test import support, mock_socket
Serhiy Storchaka16994912020-04-25 10:06:29 +030023from test.support import socket_helper
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +010024from test.support import threading_setup, threading_cleanup, join_thread
Christian Heimesc64a1a62019-09-25 16:30:20 +020025from test.support import requires_hashdigest
Pablo Aguiard5fbe9b2018-09-08 00:04:48 +020026from unittest.mock import Mock
Guido van Rossumd8faa362007-04-27 19:54:29 +000027
Serhiy Storchaka16994912020-04-25 10:06:29 +030028HOST = socket_helper.HOST
Victor Stinner45df8202010-04-28 22:31:17 +000029
Josiah Carlsond74900e2008-07-07 04:15:08 +000030if sys.platform == 'darwin':
31 # select.poll returns a select.POLLHUP at the end of the tests
32 # on darwin, so just ignore it
33 def handle_expt(self):
34 pass
35 smtpd.SMTPChannel.handle_expt = handle_expt
36
37
Christian Heimes5e696852008-04-09 08:37:03 +000038def server(evt, buf, serv):
Charles-François Natali6e204602014-07-23 19:28:13 +010039 serv.listen()
Christian Heimes380f7f22008-02-28 11:19:05 +000040 evt.set()
Guido van Rossumd8faa362007-04-27 19:54:29 +000041 try:
42 conn, addr = serv.accept()
43 except socket.timeout:
44 pass
45 else:
Guido van Rossum806c2462007-08-06 23:33:07 +000046 n = 500
47 while buf and n > 0:
48 r, w, e = select.select([], [conn], [])
49 if w:
50 sent = conn.send(buf)
51 buf = buf[sent:]
52
53 n -= 1
Guido van Rossum806c2462007-08-06 23:33:07 +000054
Guido van Rossumd8faa362007-04-27 19:54:29 +000055 conn.close()
56 finally:
57 serv.close()
58 evt.set()
59
Dong-hee Na65a5ce22020-01-15 06:42:09 +090060class GeneralTests:
Guido van Rossumd8faa362007-04-27 19:54:29 +000061
62 def setUp(self):
Richard Jones64b02de2010-08-03 06:39:33 +000063 smtplib.socket = mock_socket
64 self.port = 25
Guido van Rossumd8faa362007-04-27 19:54:29 +000065
66 def tearDown(self):
Richard Jones64b02de2010-08-03 06:39:33 +000067 smtplib.socket = socket
Guido van Rossumd8faa362007-04-27 19:54:29 +000068
R. David Murray7dff9e02010-11-08 17:15:13 +000069 # This method is no longer used but is retained for backward compatibility,
70 # so test to make sure it still works.
71 def testQuoteData(self):
72 teststr = "abc\n.jkl\rfoo\r\n..blue"
73 expected = "abc\r\n..jkl\r\nfoo\r\n...blue"
74 self.assertEqual(expected, smtplib.quotedata(teststr))
75
Guido van Rossum806c2462007-08-06 23:33:07 +000076 def testBasic1(self):
Richard Jones64b02de2010-08-03 06:39:33 +000077 mock_socket.reply_with(b"220 Hola mundo")
Guido van Rossumd8faa362007-04-27 19:54:29 +000078 # connects
Dong-hee Na65a5ce22020-01-15 06:42:09 +090079 client = self.client(HOST, self.port)
80 client.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +000081
Senthil Kumaran3d23fd62011-07-30 10:56:50 +080082 def testSourceAddress(self):
83 mock_socket.reply_with(b"220 Hola mundo")
84 # connects
Dong-hee Na65a5ce22020-01-15 06:42:09 +090085 client = self.client(HOST, self.port,
86 source_address=('127.0.0.1',19876))
87 self.assertEqual(client.source_address, ('127.0.0.1', 19876))
88 client.close()
Senthil Kumaran3d23fd62011-07-30 10:56:50 +080089
Guido van Rossum806c2462007-08-06 23:33:07 +000090 def testBasic2(self):
Richard Jones6a9e6bb2010-08-04 12:27:36 +000091 mock_socket.reply_with(b"220 Hola mundo")
Guido van Rossum806c2462007-08-06 23:33:07 +000092 # connects, include port in host name
Dong-hee Na65a5ce22020-01-15 06:42:09 +090093 client = self.client("%s:%s" % (HOST, self.port))
94 client.close()
Guido van Rossum806c2462007-08-06 23:33:07 +000095
96 def testLocalHostName(self):
Richard Jones6a9e6bb2010-08-04 12:27:36 +000097 mock_socket.reply_with(b"220 Hola mundo")
Guido van Rossum806c2462007-08-06 23:33:07 +000098 # check that supplied local_hostname is used
Dong-hee Na65a5ce22020-01-15 06:42:09 +090099 client = self.client(HOST, self.port, local_hostname="testhost")
100 self.assertEqual(client.local_hostname, "testhost")
101 client.close()
Guido van Rossum806c2462007-08-06 23:33:07 +0000102
Guido van Rossumd8faa362007-04-27 19:54:29 +0000103 def testTimeoutDefault(self):
Richard Jones6a9e6bb2010-08-04 12:27:36 +0000104 mock_socket.reply_with(b"220 Hola mundo")
Serhiy Storchaka578c6772014-02-08 15:06:08 +0200105 self.assertIsNone(mock_socket.getdefaulttimeout())
Richard Jones64b02de2010-08-03 06:39:33 +0000106 mock_socket.setdefaulttimeout(30)
107 self.assertEqual(mock_socket.getdefaulttimeout(), 30)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000108 try:
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900109 client = self.client(HOST, self.port)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000110 finally:
Richard Jones64b02de2010-08-03 06:39:33 +0000111 mock_socket.setdefaulttimeout(None)
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900112 self.assertEqual(client.sock.gettimeout(), 30)
113 client.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000114
115 def testTimeoutNone(self):
Richard Jones6a9e6bb2010-08-04 12:27:36 +0000116 mock_socket.reply_with(b"220 Hola mundo")
Serhiy Storchaka578c6772014-02-08 15:06:08 +0200117 self.assertIsNone(socket.getdefaulttimeout())
Guido van Rossumd8faa362007-04-27 19:54:29 +0000118 socket.setdefaulttimeout(30)
119 try:
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900120 client = self.client(HOST, self.port, timeout=None)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000121 finally:
Georg Brandlf78e02b2008-06-10 17:40:04 +0000122 socket.setdefaulttimeout(None)
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900123 self.assertIsNone(client.sock.gettimeout())
124 client.close()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000125
Dong-hee Na62e39732020-01-14 16:49:59 +0900126 def testTimeoutZero(self):
127 mock_socket.reply_with(b"220 Hola mundo")
128 with self.assertRaises(ValueError):
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900129 self.client(HOST, self.port, timeout=0)
Dong-hee Na62e39732020-01-14 16:49:59 +0900130
Georg Brandlf78e02b2008-06-10 17:40:04 +0000131 def testTimeoutValue(self):
Richard Jones6a9e6bb2010-08-04 12:27:36 +0000132 mock_socket.reply_with(b"220 Hola mundo")
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900133 client = self.client(HOST, self.port, timeout=30)
134 self.assertEqual(client.sock.gettimeout(), 30)
135 client.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000136
R David Murray0c49b892015-04-16 17:14:42 -0400137 def test_debuglevel(self):
138 mock_socket.reply_with(b"220 Hello world")
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900139 client = self.client()
140 client.set_debuglevel(1)
R David Murray0c49b892015-04-16 17:14:42 -0400141 with support.captured_stderr() as stderr:
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900142 client.connect(HOST, self.port)
143 client.close()
R David Murray0c49b892015-04-16 17:14:42 -0400144 expected = re.compile(r"^connect:", re.MULTILINE)
145 self.assertRegex(stderr.getvalue(), expected)
146
147 def test_debuglevel_2(self):
148 mock_socket.reply_with(b"220 Hello world")
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900149 client = self.client()
150 client.set_debuglevel(2)
R David Murray0c49b892015-04-16 17:14:42 -0400151 with support.captured_stderr() as stderr:
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900152 client.connect(HOST, self.port)
153 client.close()
R David Murray0c49b892015-04-16 17:14:42 -0400154 expected = re.compile(r"^\d{2}:\d{2}:\d{2}\.\d{6} connect: ",
155 re.MULTILINE)
156 self.assertRegex(stderr.getvalue(), expected)
157
Guido van Rossumd8faa362007-04-27 19:54:29 +0000158
Dong-hee Na65a5ce22020-01-15 06:42:09 +0900159class SMTPGeneralTests(GeneralTests, unittest.TestCase):
160
161 client = smtplib.SMTP
162
163
164class LMTPGeneralTests(GeneralTests, unittest.TestCase):
165
166 client = smtplib.LMTP
167
168 def testTimeoutZero(self):
169 super().testTimeoutZero()
170 local_host = '/some/local/lmtp/delivery/program'
171 with self.assertRaises(ValueError):
172 self.client(local_host, timeout=0)
173
Guido van Rossum04110fb2007-08-24 16:32:05 +0000174# Test server thread using the specified SMTP server class
Christian Heimes5e696852008-04-09 08:37:03 +0000175def debugging_server(serv, serv_evt, client_evt):
Christian Heimes380f7f22008-02-28 11:19:05 +0000176 serv_evt.set()
Guido van Rossum806c2462007-08-06 23:33:07 +0000177
178 try:
179 if hasattr(select, 'poll'):
180 poll_fun = asyncore.poll2
181 else:
182 poll_fun = asyncore.poll
183
184 n = 1000
185 while asyncore.socket_map and n > 0:
186 poll_fun(0.01, asyncore.socket_map)
187
188 # when the client conversation is finished, it will
189 # set client_evt, and it's then ok to kill the server
Benjamin Peterson672b8032008-06-11 19:14:14 +0000190 if client_evt.is_set():
Guido van Rossum806c2462007-08-06 23:33:07 +0000191 serv.close()
192 break
193
194 n -= 1
195
196 except socket.timeout:
197 pass
198 finally:
Benjamin Peterson672b8032008-06-11 19:14:14 +0000199 if not client_evt.is_set():
Christian Heimes380f7f22008-02-28 11:19:05 +0000200 # allow some time for the client to read the result
201 time.sleep(0.5)
202 serv.close()
Guido van Rossum806c2462007-08-06 23:33:07 +0000203 asyncore.close_all()
Guido van Rossum806c2462007-08-06 23:33:07 +0000204 serv_evt.set()
205
206MSG_BEGIN = '---------- MESSAGE FOLLOWS ----------\n'
207MSG_END = '------------ END MESSAGE ------------\n'
208
Guido van Rossum04110fb2007-08-24 16:32:05 +0000209# NOTE: Some SMTP objects in the tests below are created with a non-default
210# local_hostname argument to the constructor, since (on some systems) the FQDN
211# lookup caused by the default local_hostname sometimes takes so long that the
Guido van Rossum806c2462007-08-06 23:33:07 +0000212# test server times out, causing the test to fail.
Guido van Rossum04110fb2007-08-24 16:32:05 +0000213
214# Test behavior of smtpd.DebuggingServer
Victor Stinner45df8202010-04-28 22:31:17 +0000215class DebuggingServerTests(unittest.TestCase):
Guido van Rossum806c2462007-08-06 23:33:07 +0000216
R. David Murray7dff9e02010-11-08 17:15:13 +0000217 maxDiff = None
218
Guido van Rossum806c2462007-08-06 23:33:07 +0000219 def setUp(self):
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100220 self.thread_key = threading_setup()
Richard Jones64b02de2010-08-03 06:39:33 +0000221 self.real_getfqdn = socket.getfqdn
222 socket.getfqdn = mock_socket.getfqdn
Guido van Rossum806c2462007-08-06 23:33:07 +0000223 # temporarily replace sys.stdout to capture DebuggingServer output
224 self.old_stdout = sys.stdout
225 self.output = io.StringIO()
226 sys.stdout = self.output
227
228 self.serv_evt = threading.Event()
229 self.client_evt = threading.Event()
R. David Murray7dff9e02010-11-08 17:15:13 +0000230 # Capture SMTPChannel debug output
231 self.old_DEBUGSTREAM = smtpd.DEBUGSTREAM
232 smtpd.DEBUGSTREAM = io.StringIO()
Antoine Pitrou043bad02010-04-30 23:20:15 +0000233 # Pick a random unused port by passing 0 for the port number
R David Murray1144da52014-06-11 12:27:40 -0400234 self.serv = smtpd.DebuggingServer((HOST, 0), ('nowhere', -1),
235 decode_data=True)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700236 # Keep a note of what server host and port were assigned
237 self.host, self.port = self.serv.socket.getsockname()[:2]
Christian Heimes5e696852008-04-09 08:37:03 +0000238 serv_args = (self.serv, self.serv_evt, self.client_evt)
Antoine Pitrouc3d47722009-10-27 19:49:45 +0000239 self.thread = threading.Thread(target=debugging_server, args=serv_args)
240 self.thread.start()
Guido van Rossum806c2462007-08-06 23:33:07 +0000241
242 # wait until server thread has assigned a port number
Christian Heimes380f7f22008-02-28 11:19:05 +0000243 self.serv_evt.wait()
244 self.serv_evt.clear()
Guido van Rossum806c2462007-08-06 23:33:07 +0000245
246 def tearDown(self):
Richard Jones64b02de2010-08-03 06:39:33 +0000247 socket.getfqdn = self.real_getfqdn
Guido van Rossum806c2462007-08-06 23:33:07 +0000248 # indicate that the client is finished
249 self.client_evt.set()
250 # wait for the server thread to terminate
251 self.serv_evt.wait()
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100252 join_thread(self.thread)
Guido van Rossum806c2462007-08-06 23:33:07 +0000253 # restore sys.stdout
254 sys.stdout = self.old_stdout
R. David Murray7dff9e02010-11-08 17:15:13 +0000255 # restore DEBUGSTREAM
256 smtpd.DEBUGSTREAM.close()
257 smtpd.DEBUGSTREAM = self.old_DEBUGSTREAM
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100258 del self.thread
259 self.doCleanups()
260 threading_cleanup(*self.thread_key)
Guido van Rossum806c2462007-08-06 23:33:07 +0000261
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700262 def get_output_without_xpeer(self):
263 test_output = self.output.getvalue()
264 return re.sub(r'(.*?)^X-Peer:\s*\S+\n(.*)', r'\1\2',
265 test_output, flags=re.MULTILINE|re.DOTALL)
266
Guido van Rossum806c2462007-08-06 23:33:07 +0000267 def testBasic(self):
268 # connect
Victor Stinner07871b22019-12-10 20:32:59 +0100269 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
270 timeout=support.LOOPBACK_TIMEOUT)
Guido van Rossum806c2462007-08-06 23:33:07 +0000271 smtp.quit()
272
Senthil Kumaran3d23fd62011-07-30 10:56:50 +0800273 def testSourceAddress(self):
274 # connect
Serhiy Storchaka16994912020-04-25 10:06:29 +0300275 src_port = socket_helper.find_unused_port()
Senthil Kumaranb351a482011-07-31 09:14:17 +0800276 try:
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700277 smtp = smtplib.SMTP(self.host, self.port, local_hostname='localhost',
Victor Stinner07871b22019-12-10 20:32:59 +0100278 timeout=support.LOOPBACK_TIMEOUT,
279 source_address=(self.host, src_port))
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100280 self.addCleanup(smtp.close)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700281 self.assertEqual(smtp.source_address, (self.host, src_port))
Senthil Kumaranb351a482011-07-31 09:14:17 +0800282 self.assertEqual(smtp.local_hostname, 'localhost')
283 smtp.quit()
Andrew Svetlovf7a17b42012-12-25 16:47:37 +0200284 except OSError as e:
Senthil Kumaranb351a482011-07-31 09:14:17 +0800285 if e.errno == errno.EADDRINUSE:
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700286 self.skipTest("couldn't bind to source port %d" % src_port)
Senthil Kumaranb351a482011-07-31 09:14:17 +0800287 raise
Senthil Kumaran3d23fd62011-07-30 10:56:50 +0800288
Guido van Rossum04110fb2007-08-24 16:32:05 +0000289 def testNOOP(self):
Victor Stinner07871b22019-12-10 20:32:59 +0100290 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
291 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100292 self.addCleanup(smtp.close)
R David Murrayd1a30c92012-05-26 14:33:59 -0400293 expected = (250, b'OK')
Guido van Rossum04110fb2007-08-24 16:32:05 +0000294 self.assertEqual(smtp.noop(), expected)
295 smtp.quit()
296
297 def testRSET(self):
Victor Stinner07871b22019-12-10 20:32:59 +0100298 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
299 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100300 self.addCleanup(smtp.close)
R David Murrayd1a30c92012-05-26 14:33:59 -0400301 expected = (250, b'OK')
Guido van Rossum04110fb2007-08-24 16:32:05 +0000302 self.assertEqual(smtp.rset(), expected)
303 smtp.quit()
304
Benjamin Peterson1eca0622013-09-29 10:46:31 -0400305 def testELHO(self):
Guido van Rossum04110fb2007-08-24 16:32:05 +0000306 # EHLO isn't implemented in DebuggingServer
Victor Stinner07871b22019-12-10 20:32:59 +0100307 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
308 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100309 self.addCleanup(smtp.close)
Benjamin Peterson1eca0622013-09-29 10:46:31 -0400310 expected = (250, b'\nSIZE 33554432\nHELP')
Guido van Rossum806c2462007-08-06 23:33:07 +0000311 self.assertEqual(smtp.ehlo(), expected)
312 smtp.quit()
313
Benjamin Peterson1eca0622013-09-29 10:46:31 -0400314 def testEXPNNotImplemented(self):
R David Murrayd1a30c92012-05-26 14:33:59 -0400315 # EXPN isn't implemented in DebuggingServer
Victor Stinner07871b22019-12-10 20:32:59 +0100316 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
317 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100318 self.addCleanup(smtp.close)
R David Murrayd1a30c92012-05-26 14:33:59 -0400319 expected = (502, b'EXPN not implemented')
320 smtp.putcmd('EXPN')
321 self.assertEqual(smtp.getreply(), expected)
322 smtp.quit()
323
324 def testVRFY(self):
Victor Stinner07871b22019-12-10 20:32:59 +0100325 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
326 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100327 self.addCleanup(smtp.close)
R David Murrayd1a30c92012-05-26 14:33:59 -0400328 expected = (252, b'Cannot VRFY user, but will accept message ' + \
329 b'and attempt delivery')
Guido van Rossum04110fb2007-08-24 16:32:05 +0000330 self.assertEqual(smtp.vrfy('nobody@nowhere.com'), expected)
331 self.assertEqual(smtp.verify('nobody@nowhere.com'), expected)
332 smtp.quit()
333
334 def testSecondHELO(self):
335 # check that a second HELO returns a message that it's a duplicate
336 # (this behavior is specific to smtpd.SMTPChannel)
Victor Stinner07871b22019-12-10 20:32:59 +0100337 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
338 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100339 self.addCleanup(smtp.close)
Guido van Rossum04110fb2007-08-24 16:32:05 +0000340 smtp.helo()
341 expected = (503, b'Duplicate HELO/EHLO')
342 self.assertEqual(smtp.helo(), expected)
343 smtp.quit()
344
Guido van Rossum806c2462007-08-06 23:33:07 +0000345 def testHELP(self):
Victor Stinner07871b22019-12-10 20:32:59 +0100346 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
347 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100348 self.addCleanup(smtp.close)
R David Murrayd1a30c92012-05-26 14:33:59 -0400349 self.assertEqual(smtp.help(), b'Supported commands: EHLO HELO MAIL ' + \
350 b'RCPT DATA RSET NOOP QUIT VRFY')
Guido van Rossum806c2462007-08-06 23:33:07 +0000351 smtp.quit()
352
353 def testSend(self):
354 # connect and send mail
355 m = 'A test message'
Victor Stinner07871b22019-12-10 20:32:59 +0100356 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
357 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100358 self.addCleanup(smtp.close)
Guido van Rossum806c2462007-08-06 23:33:07 +0000359 smtp.sendmail('John', 'Sally', m)
Neal Norwitz25329672008-08-25 03:55:03 +0000360 # XXX(nnorwitz): this test is flaky and dies with a bad file descriptor
361 # in asyncore. This sleep might help, but should really be fixed
362 # properly by using an Event variable.
363 time.sleep(0.01)
Guido van Rossum806c2462007-08-06 23:33:07 +0000364 smtp.quit()
365
366 self.client_evt.set()
367 self.serv_evt.wait()
368 self.output.flush()
369 mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
370 self.assertEqual(self.output.getvalue(), mexpect)
371
R. David Murray7dff9e02010-11-08 17:15:13 +0000372 def testSendBinary(self):
373 m = b'A test message'
Victor Stinner07871b22019-12-10 20:32:59 +0100374 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
375 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100376 self.addCleanup(smtp.close)
R. David Murray7dff9e02010-11-08 17:15:13 +0000377 smtp.sendmail('John', 'Sally', m)
378 # XXX (see comment in testSend)
379 time.sleep(0.01)
380 smtp.quit()
381
382 self.client_evt.set()
383 self.serv_evt.wait()
384 self.output.flush()
385 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.decode('ascii'), MSG_END)
386 self.assertEqual(self.output.getvalue(), mexpect)
387
R David Murray0f663d02011-06-09 15:05:57 -0400388 def testSendNeedingDotQuote(self):
389 # Issue 12283
390 m = '.A test\n.mes.sage.'
Victor Stinner07871b22019-12-10 20:32:59 +0100391 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
392 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100393 self.addCleanup(smtp.close)
R David Murray0f663d02011-06-09 15:05:57 -0400394 smtp.sendmail('John', 'Sally', m)
395 # XXX (see comment in testSend)
396 time.sleep(0.01)
397 smtp.quit()
398
399 self.client_evt.set()
400 self.serv_evt.wait()
401 self.output.flush()
402 mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
403 self.assertEqual(self.output.getvalue(), mexpect)
404
R David Murray46346762011-07-18 21:38:54 -0400405 def testSendNullSender(self):
406 m = 'A test message'
Victor Stinner07871b22019-12-10 20:32:59 +0100407 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
408 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100409 self.addCleanup(smtp.close)
R David Murray46346762011-07-18 21:38:54 -0400410 smtp.sendmail('<>', 'Sally', m)
411 # XXX (see comment in testSend)
412 time.sleep(0.01)
413 smtp.quit()
414
415 self.client_evt.set()
416 self.serv_evt.wait()
417 self.output.flush()
418 mexpect = '%s%s\n%s' % (MSG_BEGIN, m, MSG_END)
419 self.assertEqual(self.output.getvalue(), mexpect)
420 debugout = smtpd.DEBUGSTREAM.getvalue()
421 sender = re.compile("^sender: <>$", re.MULTILINE)
422 self.assertRegex(debugout, sender)
423
R. David Murray7dff9e02010-11-08 17:15:13 +0000424 def testSendMessage(self):
425 m = email.mime.text.MIMEText('A test message')
Victor Stinner07871b22019-12-10 20:32:59 +0100426 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
427 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100428 self.addCleanup(smtp.close)
R. David Murray7dff9e02010-11-08 17:15:13 +0000429 smtp.send_message(m, from_addr='John', to_addrs='Sally')
430 # XXX (see comment in testSend)
431 time.sleep(0.01)
432 smtp.quit()
433
434 self.client_evt.set()
435 self.serv_evt.wait()
436 self.output.flush()
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700437 # Remove the X-Peer header that DebuggingServer adds as figuring out
438 # exactly what IP address format is put there is not easy (and
439 # irrelevant to our test). Typically 127.0.0.1 or ::1, but it is
440 # not always the same as socket.gethostbyname(HOST). :(
441 test_output = self.get_output_without_xpeer()
442 del m['X-Peer']
R. David Murray7dff9e02010-11-08 17:15:13 +0000443 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700444 self.assertEqual(test_output, mexpect)
R. David Murray7dff9e02010-11-08 17:15:13 +0000445
446 def testSendMessageWithAddresses(self):
447 m = email.mime.text.MIMEText('A test message')
448 m['From'] = 'foo@bar.com'
449 m['To'] = 'John'
450 m['CC'] = 'Sally, Fred'
451 m['Bcc'] = 'John Root <root@localhost>, "Dinsdale" <warped@silly.walks.com>'
Victor Stinner07871b22019-12-10 20:32:59 +0100452 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
453 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100454 self.addCleanup(smtp.close)
R. David Murray7dff9e02010-11-08 17:15:13 +0000455 smtp.send_message(m)
456 # XXX (see comment in testSend)
457 time.sleep(0.01)
458 smtp.quit()
R David Murrayac4e5ab2011-07-02 21:03:19 -0400459 # make sure the Bcc header is still in the message.
460 self.assertEqual(m['Bcc'], 'John Root <root@localhost>, "Dinsdale" '
461 '<warped@silly.walks.com>')
R. David Murray7dff9e02010-11-08 17:15:13 +0000462
463 self.client_evt.set()
464 self.serv_evt.wait()
465 self.output.flush()
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700466 # Remove the X-Peer header that DebuggingServer adds.
467 test_output = self.get_output_without_xpeer()
468 del m['X-Peer']
R David Murrayac4e5ab2011-07-02 21:03:19 -0400469 # The Bcc header should not be transmitted.
R. David Murray7dff9e02010-11-08 17:15:13 +0000470 del m['Bcc']
471 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700472 self.assertEqual(test_output, mexpect)
R. David Murray7dff9e02010-11-08 17:15:13 +0000473 debugout = smtpd.DEBUGSTREAM.getvalue()
474 sender = re.compile("^sender: foo@bar.com$", re.MULTILINE)
Ezio Melottied3a7d22010-12-01 02:32:32 +0000475 self.assertRegex(debugout, sender)
R. David Murray7dff9e02010-11-08 17:15:13 +0000476 for addr in ('John', 'Sally', 'Fred', 'root@localhost',
477 'warped@silly.walks.com'):
478 to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr),
479 re.MULTILINE)
Ezio Melottied3a7d22010-12-01 02:32:32 +0000480 self.assertRegex(debugout, to_addr)
R. David Murray7dff9e02010-11-08 17:15:13 +0000481
482 def testSendMessageWithSomeAddresses(self):
483 # Make sure nothing breaks if not all of the three 'to' headers exist
484 m = email.mime.text.MIMEText('A test message')
485 m['From'] = 'foo@bar.com'
486 m['To'] = 'John, Dinsdale'
Victor Stinner07871b22019-12-10 20:32:59 +0100487 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
488 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100489 self.addCleanup(smtp.close)
R. David Murray7dff9e02010-11-08 17:15:13 +0000490 smtp.send_message(m)
491 # XXX (see comment in testSend)
492 time.sleep(0.01)
493 smtp.quit()
494
495 self.client_evt.set()
496 self.serv_evt.wait()
497 self.output.flush()
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700498 # Remove the X-Peer header that DebuggingServer adds.
499 test_output = self.get_output_without_xpeer()
500 del m['X-Peer']
R. David Murray7dff9e02010-11-08 17:15:13 +0000501 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700502 self.assertEqual(test_output, mexpect)
R. David Murray7dff9e02010-11-08 17:15:13 +0000503 debugout = smtpd.DEBUGSTREAM.getvalue()
504 sender = re.compile("^sender: foo@bar.com$", re.MULTILINE)
Ezio Melottied3a7d22010-12-01 02:32:32 +0000505 self.assertRegex(debugout, sender)
R. David Murray7dff9e02010-11-08 17:15:13 +0000506 for addr in ('John', 'Dinsdale'):
507 to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr),
508 re.MULTILINE)
Ezio Melottied3a7d22010-12-01 02:32:32 +0000509 self.assertRegex(debugout, to_addr)
R. David Murray7dff9e02010-11-08 17:15:13 +0000510
R David Murrayac4e5ab2011-07-02 21:03:19 -0400511 def testSendMessageWithSpecifiedAddresses(self):
512 # Make sure addresses specified in call override those in message.
513 m = email.mime.text.MIMEText('A test message')
514 m['From'] = 'foo@bar.com'
515 m['To'] = 'John, Dinsdale'
Victor Stinner07871b22019-12-10 20:32:59 +0100516 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
517 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100518 self.addCleanup(smtp.close)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400519 smtp.send_message(m, from_addr='joe@example.com', to_addrs='foo@example.net')
520 # XXX (see comment in testSend)
521 time.sleep(0.01)
522 smtp.quit()
523
524 self.client_evt.set()
525 self.serv_evt.wait()
526 self.output.flush()
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700527 # Remove the X-Peer header that DebuggingServer adds.
528 test_output = self.get_output_without_xpeer()
529 del m['X-Peer']
R David Murrayac4e5ab2011-07-02 21:03:19 -0400530 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700531 self.assertEqual(test_output, mexpect)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400532 debugout = smtpd.DEBUGSTREAM.getvalue()
533 sender = re.compile("^sender: joe@example.com$", re.MULTILINE)
534 self.assertRegex(debugout, sender)
535 for addr in ('John', 'Dinsdale'):
536 to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr),
537 re.MULTILINE)
538 self.assertNotRegex(debugout, to_addr)
539 recip = re.compile(r"^recips: .*'foo@example.net'.*$", re.MULTILINE)
540 self.assertRegex(debugout, recip)
541
542 def testSendMessageWithMultipleFrom(self):
543 # Sender overrides To
544 m = email.mime.text.MIMEText('A test message')
545 m['From'] = 'Bernard, Bianca'
546 m['Sender'] = 'the_rescuers@Rescue-Aid-Society.com'
547 m['To'] = 'John, Dinsdale'
Victor Stinner07871b22019-12-10 20:32:59 +0100548 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
549 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100550 self.addCleanup(smtp.close)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400551 smtp.send_message(m)
552 # XXX (see comment in testSend)
553 time.sleep(0.01)
554 smtp.quit()
555
556 self.client_evt.set()
557 self.serv_evt.wait()
558 self.output.flush()
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700559 # Remove the X-Peer header that DebuggingServer adds.
560 test_output = self.get_output_without_xpeer()
561 del m['X-Peer']
R David Murrayac4e5ab2011-07-02 21:03:19 -0400562 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700563 self.assertEqual(test_output, mexpect)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400564 debugout = smtpd.DEBUGSTREAM.getvalue()
565 sender = re.compile("^sender: the_rescuers@Rescue-Aid-Society.com$", re.MULTILINE)
566 self.assertRegex(debugout, sender)
567 for addr in ('John', 'Dinsdale'):
568 to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr),
569 re.MULTILINE)
570 self.assertRegex(debugout, to_addr)
571
572 def testSendMessageResent(self):
573 m = email.mime.text.MIMEText('A test message')
574 m['From'] = 'foo@bar.com'
575 m['To'] = 'John'
576 m['CC'] = 'Sally, Fred'
577 m['Bcc'] = 'John Root <root@localhost>, "Dinsdale" <warped@silly.walks.com>'
578 m['Resent-Date'] = 'Thu, 1 Jan 1970 17:42:00 +0000'
579 m['Resent-From'] = 'holy@grail.net'
580 m['Resent-To'] = 'Martha <my_mom@great.cooker.com>, Jeff'
581 m['Resent-Bcc'] = 'doe@losthope.net'
Victor Stinner07871b22019-12-10 20:32:59 +0100582 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
583 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100584 self.addCleanup(smtp.close)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400585 smtp.send_message(m)
586 # XXX (see comment in testSend)
587 time.sleep(0.01)
588 smtp.quit()
589
590 self.client_evt.set()
591 self.serv_evt.wait()
592 self.output.flush()
593 # The Resent-Bcc headers are deleted before serialization.
594 del m['Bcc']
595 del m['Resent-Bcc']
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700596 # Remove the X-Peer header that DebuggingServer adds.
597 test_output = self.get_output_without_xpeer()
598 del m['X-Peer']
R David Murrayac4e5ab2011-07-02 21:03:19 -0400599 mexpect = '%s%s\n%s' % (MSG_BEGIN, m.as_string(), MSG_END)
Gregory P. Smithefb1d0a2017-09-09 00:30:15 -0700600 self.assertEqual(test_output, mexpect)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400601 debugout = smtpd.DEBUGSTREAM.getvalue()
602 sender = re.compile("^sender: holy@grail.net$", re.MULTILINE)
603 self.assertRegex(debugout, sender)
604 for addr in ('my_mom@great.cooker.com', 'Jeff', 'doe@losthope.net'):
605 to_addr = re.compile(r"^recips: .*'{}'.*$".format(addr),
606 re.MULTILINE)
607 self.assertRegex(debugout, to_addr)
608
609 def testSendMessageMultipleResentRaises(self):
610 m = email.mime.text.MIMEText('A test message')
611 m['From'] = 'foo@bar.com'
612 m['To'] = 'John'
613 m['CC'] = 'Sally, Fred'
614 m['Bcc'] = 'John Root <root@localhost>, "Dinsdale" <warped@silly.walks.com>'
615 m['Resent-Date'] = 'Thu, 1 Jan 1970 17:42:00 +0000'
616 m['Resent-From'] = 'holy@grail.net'
617 m['Resent-To'] = 'Martha <my_mom@great.cooker.com>, Jeff'
618 m['Resent-Bcc'] = 'doe@losthope.net'
619 m['Resent-Date'] = 'Thu, 2 Jan 1970 17:42:00 +0000'
620 m['Resent-To'] = 'holy@grail.net'
621 m['Resent-From'] = 'Martha <my_mom@great.cooker.com>, Jeff'
Victor Stinner07871b22019-12-10 20:32:59 +0100622 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
623 timeout=support.LOOPBACK_TIMEOUT)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100624 self.addCleanup(smtp.close)
R David Murrayac4e5ab2011-07-02 21:03:19 -0400625 with self.assertRaises(ValueError):
626 smtp.send_message(m)
627 smtp.close()
Guido van Rossum806c2462007-08-06 23:33:07 +0000628
Victor Stinner45df8202010-04-28 22:31:17 +0000629class NonConnectingTests(unittest.TestCase):
Christian Heimes380f7f22008-02-28 11:19:05 +0000630
631 def testNotConnected(self):
632 # Test various operations on an unconnected SMTP object that
633 # should raise exceptions (at present the attempt in SMTP.send
634 # to reference the nonexistent 'sock' attribute of the SMTP object
635 # causes an AttributeError)
636 smtp = smtplib.SMTP()
637 self.assertRaises(smtplib.SMTPServerDisconnected, smtp.ehlo)
638 self.assertRaises(smtplib.SMTPServerDisconnected,
639 smtp.send, 'test msg')
640
641 def testNonnumericPort(self):
Andrew Svetlov0832af62012-12-18 23:10:48 +0200642 # check that non-numeric port raises OSError
Andrew Svetlov2ade6f22012-12-17 18:57:16 +0200643 self.assertRaises(OSError, smtplib.SMTP,
Christian Heimes380f7f22008-02-28 11:19:05 +0000644 "localhost", "bogus")
Andrew Svetlov2ade6f22012-12-17 18:57:16 +0200645 self.assertRaises(OSError, smtplib.SMTP,
Christian Heimes380f7f22008-02-28 11:19:05 +0000646 "localhost:bogus")
647
Romuald Brunet7b313972018-10-09 16:31:55 +0200648 def testSockAttributeExists(self):
649 # check that sock attribute is present outside of a connect() call
650 # (regression test, the previous behavior raised an
651 # AttributeError: 'SMTP' object has no attribute 'sock')
652 with smtplib.SMTP() as smtp:
653 self.assertIsNone(smtp.sock)
654
Christian Heimes380f7f22008-02-28 11:19:05 +0000655
Pablo Aguiard5fbe9b2018-09-08 00:04:48 +0200656class DefaultArgumentsTests(unittest.TestCase):
657
658 def setUp(self):
659 self.msg = EmailMessage()
660 self.msg['From'] = 'Páolo <főo@bar.com>'
661 self.smtp = smtplib.SMTP()
662 self.smtp.ehlo = Mock(return_value=(200, 'OK'))
663 self.smtp.has_extn, self.smtp.sendmail = Mock(), Mock()
664
665 def testSendMessage(self):
666 expected_mail_options = ('SMTPUTF8', 'BODY=8BITMIME')
667 self.smtp.send_message(self.msg)
668 self.smtp.send_message(self.msg)
669 self.assertEqual(self.smtp.sendmail.call_args_list[0][0][3],
670 expected_mail_options)
671 self.assertEqual(self.smtp.sendmail.call_args_list[1][0][3],
672 expected_mail_options)
673
674 def testSendMessageWithMailOptions(self):
675 mail_options = ['STARTTLS']
676 expected_mail_options = ('STARTTLS', 'SMTPUTF8', 'BODY=8BITMIME')
677 self.smtp.send_message(self.msg, None, None, mail_options)
678 self.assertEqual(mail_options, ['STARTTLS'])
679 self.assertEqual(self.smtp.sendmail.call_args_list[0][0][3],
680 expected_mail_options)
681
682
Guido van Rossum04110fb2007-08-24 16:32:05 +0000683# test response of client to a non-successful HELO message
Victor Stinner45df8202010-04-28 22:31:17 +0000684class BadHELOServerTests(unittest.TestCase):
Guido van Rossum806c2462007-08-06 23:33:07 +0000685
686 def setUp(self):
Richard Jones64b02de2010-08-03 06:39:33 +0000687 smtplib.socket = mock_socket
688 mock_socket.reply_with(b"199 no hello for you!")
Guido van Rossum806c2462007-08-06 23:33:07 +0000689 self.old_stdout = sys.stdout
690 self.output = io.StringIO()
691 sys.stdout = self.output
Richard Jones64b02de2010-08-03 06:39:33 +0000692 self.port = 25
Guido van Rossum806c2462007-08-06 23:33:07 +0000693
694 def tearDown(self):
Richard Jones64b02de2010-08-03 06:39:33 +0000695 smtplib.socket = socket
Guido van Rossum806c2462007-08-06 23:33:07 +0000696 sys.stdout = self.old_stdout
697
698 def testFailingHELO(self):
699 self.assertRaises(smtplib.SMTPConnectError, smtplib.SMTP,
Christian Heimes5e696852008-04-09 08:37:03 +0000700 HOST, self.port, 'localhost', 3)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000701
Guido van Rossum04110fb2007-08-24 16:32:05 +0000702
Georg Brandlb38b5c42014-02-10 22:11:21 +0100703class TooLongLineTests(unittest.TestCase):
704 respdata = b'250 OK' + (b'.' * smtplib._MAXLINE * 2) + b'\n'
705
706 def setUp(self):
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100707 self.thread_key = threading_setup()
Georg Brandlb38b5c42014-02-10 22:11:21 +0100708 self.old_stdout = sys.stdout
709 self.output = io.StringIO()
710 sys.stdout = self.output
711
712 self.evt = threading.Event()
713 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
714 self.sock.settimeout(15)
Serhiy Storchaka16994912020-04-25 10:06:29 +0300715 self.port = socket_helper.bind_port(self.sock)
Georg Brandlb38b5c42014-02-10 22:11:21 +0100716 servargs = (self.evt, self.respdata, self.sock)
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100717 self.thread = threading.Thread(target=server, args=servargs)
718 self.thread.start()
Georg Brandlb38b5c42014-02-10 22:11:21 +0100719 self.evt.wait()
720 self.evt.clear()
721
722 def tearDown(self):
723 self.evt.wait()
724 sys.stdout = self.old_stdout
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100725 join_thread(self.thread)
726 del self.thread
727 self.doCleanups()
728 threading_cleanup(*self.thread_key)
Georg Brandlb38b5c42014-02-10 22:11:21 +0100729
730 def testLineTooLong(self):
731 self.assertRaises(smtplib.SMTPResponseException, smtplib.SMTP,
732 HOST, self.port, 'localhost', 3)
733
734
Guido van Rossum04110fb2007-08-24 16:32:05 +0000735sim_users = {'Mr.A@somewhere.com':'John A',
R David Murray46346762011-07-18 21:38:54 -0400736 'Ms.B@xn--fo-fka.com':'Sally B',
Guido van Rossum04110fb2007-08-24 16:32:05 +0000737 'Mrs.C@somewhereesle.com':'Ruth C',
738 }
739
R. David Murraycaa27b72009-05-23 18:49:56 +0000740sim_auth = ('Mr.A@somewhere.com', 'somepassword')
R. David Murrayfb123912009-05-28 18:19:00 +0000741sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn'
742 'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=')
Guido van Rossum04110fb2007-08-24 16:32:05 +0000743sim_lists = {'list-1':['Mr.A@somewhere.com','Mrs.C@somewhereesle.com'],
R David Murray46346762011-07-18 21:38:54 -0400744 'list-2':['Ms.B@xn--fo-fka.com',],
Guido van Rossum04110fb2007-08-24 16:32:05 +0000745 }
746
747# Simulated SMTP channel & server
R David Murrayb0deeb42015-11-08 01:03:52 -0500748class ResponseException(Exception): pass
Guido van Rossum04110fb2007-08-24 16:32:05 +0000749class SimSMTPChannel(smtpd.SMTPChannel):
R. David Murrayfb123912009-05-28 18:19:00 +0000750
Barry Warsaw1f5c9582011-03-15 15:04:44 -0400751 quit_response = None
R David Murrayd312c742013-03-20 20:36:14 -0400752 mail_response = None
753 rcpt_response = None
754 data_response = None
755 rcpt_count = 0
756 rset_count = 0
R David Murrayafb151a2014-04-14 18:21:38 -0400757 disconnect = 0
R David Murrayb0deeb42015-11-08 01:03:52 -0500758 AUTH = 99 # Add protocol state to enable auth testing.
759 authenticated_user = None
Barry Warsaw1f5c9582011-03-15 15:04:44 -0400760
R. David Murray23ddc0e2009-05-29 18:03:16 +0000761 def __init__(self, extra_features, *args, **kw):
762 self._extrafeatures = ''.join(
763 [ "250-{0}\r\n".format(x) for x in extra_features ])
R. David Murrayfb123912009-05-28 18:19:00 +0000764 super(SimSMTPChannel, self).__init__(*args, **kw)
765
R David Murrayb0deeb42015-11-08 01:03:52 -0500766 # AUTH related stuff. It would be nice if support for this were in smtpd.
767 def found_terminator(self):
768 if self.smtp_state == self.AUTH:
769 line = self._emptystring.join(self.received_lines)
770 print('Data:', repr(line), file=smtpd.DEBUGSTREAM)
771 self.received_lines = []
772 try:
773 self.auth_object(line)
774 except ResponseException as e:
775 self.smtp_state = self.COMMAND
776 self.push('%s %s' % (e.smtp_code, e.smtp_error))
777 return
778 super().found_terminator()
779
780
781 def smtp_AUTH(self, arg):
782 if not self.seen_greeting:
783 self.push('503 Error: send EHLO first')
784 return
785 if not self.extended_smtp or 'AUTH' not in self._extrafeatures:
786 self.push('500 Error: command "AUTH" not recognized')
787 return
788 if self.authenticated_user is not None:
789 self.push(
790 '503 Bad sequence of commands: already authenticated')
791 return
792 args = arg.split()
793 if len(args) not in [1, 2]:
794 self.push('501 Syntax: AUTH <mechanism> [initial-response]')
795 return
796 auth_object_name = '_auth_%s' % args[0].lower().replace('-', '_')
797 try:
798 self.auth_object = getattr(self, auth_object_name)
799 except AttributeError:
800 self.push('504 Command parameter not implemented: unsupported '
801 ' authentication mechanism {!r}'.format(auth_object_name))
802 return
803 self.smtp_state = self.AUTH
804 self.auth_object(args[1] if len(args) == 2 else None)
805
806 def _authenticated(self, user, valid):
807 if valid:
808 self.authenticated_user = user
809 self.push('235 Authentication Succeeded')
810 else:
811 self.push('535 Authentication credentials invalid')
812 self.smtp_state = self.COMMAND
813
814 def _decode_base64(self, string):
815 return base64.decodebytes(string.encode('ascii')).decode('utf-8')
816
817 def _auth_plain(self, arg=None):
818 if arg is None:
819 self.push('334 ')
820 else:
821 logpass = self._decode_base64(arg)
822 try:
823 *_, user, password = logpass.split('\0')
824 except ValueError as e:
825 self.push('535 Splitting response {!r} into user and password'
826 ' failed: {}'.format(logpass, e))
827 return
828 self._authenticated(user, password == sim_auth[1])
829
830 def _auth_login(self, arg=None):
831 if arg is None:
832 # base64 encoded 'Username:'
833 self.push('334 VXNlcm5hbWU6')
834 elif not hasattr(self, '_auth_login_user'):
835 self._auth_login_user = self._decode_base64(arg)
836 # base64 encoded 'Password:'
837 self.push('334 UGFzc3dvcmQ6')
838 else:
839 password = self._decode_base64(arg)
840 self._authenticated(self._auth_login_user, password == sim_auth[1])
841 del self._auth_login_user
842
843 def _auth_cram_md5(self, arg=None):
844 if arg is None:
845 self.push('334 {}'.format(sim_cram_md5_challenge))
846 else:
847 logpass = self._decode_base64(arg)
848 try:
849 user, hashed_pass = logpass.split()
850 except ValueError as e:
Serhiy Storchaka34fd4c22018-11-05 16:20:25 +0200851 self.push('535 Splitting response {!r} into user and password '
R David Murrayb0deeb42015-11-08 01:03:52 -0500852 'failed: {}'.format(logpass, e))
853 return False
854 valid_hashed_pass = hmac.HMAC(
855 sim_auth[1].encode('ascii'),
856 self._decode_base64(sim_cram_md5_challenge).encode('ascii'),
857 'md5').hexdigest()
858 self._authenticated(user, hashed_pass == valid_hashed_pass)
859 # end AUTH related stuff.
860
Guido van Rossum04110fb2007-08-24 16:32:05 +0000861 def smtp_EHLO(self, arg):
R. David Murrayfb123912009-05-28 18:19:00 +0000862 resp = ('250-testhost\r\n'
863 '250-EXPN\r\n'
864 '250-SIZE 20000000\r\n'
865 '250-STARTTLS\r\n'
866 '250-DELIVERBY\r\n')
867 resp = resp + self._extrafeatures + '250 HELP'
Guido van Rossum04110fb2007-08-24 16:32:05 +0000868 self.push(resp)
R David Murrayf1a40b42013-03-20 21:12:17 -0400869 self.seen_greeting = arg
870 self.extended_smtp = True
Guido van Rossum04110fb2007-08-24 16:32:05 +0000871
872 def smtp_VRFY(self, arg):
R David Murray46346762011-07-18 21:38:54 -0400873 # For max compatibility smtplib should be sending the raw address.
874 if arg in sim_users:
875 self.push('250 %s %s' % (sim_users[arg], smtplib.quoteaddr(arg)))
Guido van Rossum04110fb2007-08-24 16:32:05 +0000876 else:
877 self.push('550 No such user: %s' % arg)
878
879 def smtp_EXPN(self, arg):
R David Murray46346762011-07-18 21:38:54 -0400880 list_name = arg.lower()
Guido van Rossum04110fb2007-08-24 16:32:05 +0000881 if list_name in sim_lists:
882 user_list = sim_lists[list_name]
883 for n, user_email in enumerate(user_list):
884 quoted_addr = smtplib.quoteaddr(user_email)
885 if n < len(user_list) - 1:
886 self.push('250-%s %s' % (sim_users[user_email], quoted_addr))
887 else:
888 self.push('250 %s %s' % (sim_users[user_email], quoted_addr))
889 else:
890 self.push('550 No access for you!')
891
Barry Warsaw1f5c9582011-03-15 15:04:44 -0400892 def smtp_QUIT(self, arg):
Barry Warsaw1f5c9582011-03-15 15:04:44 -0400893 if self.quit_response is None:
894 super(SimSMTPChannel, self).smtp_QUIT(arg)
895 else:
896 self.push(self.quit_response)
897 self.close_when_done()
898
R David Murrayd312c742013-03-20 20:36:14 -0400899 def smtp_MAIL(self, arg):
900 if self.mail_response is None:
901 super().smtp_MAIL(arg)
902 else:
903 self.push(self.mail_response)
R David Murrayafb151a2014-04-14 18:21:38 -0400904 if self.disconnect:
905 self.close_when_done()
R David Murrayd312c742013-03-20 20:36:14 -0400906
907 def smtp_RCPT(self, arg):
908 if self.rcpt_response is None:
909 super().smtp_RCPT(arg)
910 return
R David Murrayd312c742013-03-20 20:36:14 -0400911 self.rcpt_count += 1
R David Murray03b01162013-03-20 22:11:40 -0400912 self.push(self.rcpt_response[self.rcpt_count-1])
R David Murrayd312c742013-03-20 20:36:14 -0400913
914 def smtp_RSET(self, arg):
R David Murrayd312c742013-03-20 20:36:14 -0400915 self.rset_count += 1
R David Murray03b01162013-03-20 22:11:40 -0400916 super().smtp_RSET(arg)
R David Murrayd312c742013-03-20 20:36:14 -0400917
918 def smtp_DATA(self, arg):
919 if self.data_response is None:
920 super().smtp_DATA(arg)
921 else:
922 self.push(self.data_response)
923
Giampaolo Rodolàd930b632010-05-06 20:21:57 +0000924 def handle_error(self):
925 raise
926
Guido van Rossum04110fb2007-08-24 16:32:05 +0000927
928class SimSMTPServer(smtpd.SMTPServer):
R. David Murrayfb123912009-05-28 18:19:00 +0000929
R David Murrayd312c742013-03-20 20:36:14 -0400930 channel_class = SimSMTPChannel
Barry Warsaw1f5c9582011-03-15 15:04:44 -0400931
R. David Murray23ddc0e2009-05-29 18:03:16 +0000932 def __init__(self, *args, **kw):
933 self._extra_features = []
Stéphane Wirtel8d83e4b2018-01-31 01:02:51 +0100934 self._addresses = {}
R. David Murray23ddc0e2009-05-29 18:03:16 +0000935 smtpd.SMTPServer.__init__(self, *args, **kw)
936
Giampaolo Rodolà977c7072010-10-04 21:08:36 +0000937 def handle_accepted(self, conn, addr):
R David Murrayf1a40b42013-03-20 21:12:17 -0400938 self._SMTPchannel = self.channel_class(
R David Murray1144da52014-06-11 12:27:40 -0400939 self._extra_features, self, conn, addr,
940 decode_data=self._decode_data)
Guido van Rossum04110fb2007-08-24 16:32:05 +0000941
942 def process_message(self, peer, mailfrom, rcpttos, data):
Stéphane Wirtel8d83e4b2018-01-31 01:02:51 +0100943 self._addresses['from'] = mailfrom
944 self._addresses['tos'] = rcpttos
Guido van Rossum04110fb2007-08-24 16:32:05 +0000945
R. David Murrayfb123912009-05-28 18:19:00 +0000946 def add_feature(self, feature):
R. David Murray23ddc0e2009-05-29 18:03:16 +0000947 self._extra_features.append(feature)
R. David Murrayfb123912009-05-28 18:19:00 +0000948
Giampaolo Rodolàd930b632010-05-06 20:21:57 +0000949 def handle_error(self):
950 raise
951
Guido van Rossum04110fb2007-08-24 16:32:05 +0000952
953# Test various SMTP & ESMTP commands/behaviors that require a simulated server
954# (i.e., something with more features than DebuggingServer)
Victor Stinner45df8202010-04-28 22:31:17 +0000955class SMTPSimTests(unittest.TestCase):
Guido van Rossum04110fb2007-08-24 16:32:05 +0000956
957 def setUp(self):
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100958 self.thread_key = threading_setup()
Richard Jones64b02de2010-08-03 06:39:33 +0000959 self.real_getfqdn = socket.getfqdn
960 socket.getfqdn = mock_socket.getfqdn
Guido van Rossum04110fb2007-08-24 16:32:05 +0000961 self.serv_evt = threading.Event()
962 self.client_evt = threading.Event()
Antoine Pitrou043bad02010-04-30 23:20:15 +0000963 # Pick a random unused port by passing 0 for the port number
R David Murray1144da52014-06-11 12:27:40 -0400964 self.serv = SimSMTPServer((HOST, 0), ('nowhere', -1), decode_data=True)
Antoine Pitrou043bad02010-04-30 23:20:15 +0000965 # Keep a note of what port was assigned
966 self.port = self.serv.socket.getsockname()[1]
Christian Heimes5e696852008-04-09 08:37:03 +0000967 serv_args = (self.serv, self.serv_evt, self.client_evt)
Antoine Pitrouc3d47722009-10-27 19:49:45 +0000968 self.thread = threading.Thread(target=debugging_server, args=serv_args)
969 self.thread.start()
Guido van Rossum04110fb2007-08-24 16:32:05 +0000970
971 # wait until server thread has assigned a port number
Christian Heimes380f7f22008-02-28 11:19:05 +0000972 self.serv_evt.wait()
973 self.serv_evt.clear()
Guido van Rossum04110fb2007-08-24 16:32:05 +0000974
975 def tearDown(self):
Richard Jones64b02de2010-08-03 06:39:33 +0000976 socket.getfqdn = self.real_getfqdn
Guido van Rossum04110fb2007-08-24 16:32:05 +0000977 # indicate that the client is finished
978 self.client_evt.set()
979 # wait for the server thread to terminate
980 self.serv_evt.wait()
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +0100981 join_thread(self.thread)
982 del self.thread
983 self.doCleanups()
984 threading_cleanup(*self.thread_key)
Guido van Rossum04110fb2007-08-24 16:32:05 +0000985
986 def testBasic(self):
987 # smoke test
Victor Stinner7772b1a2019-12-11 22:17:04 +0100988 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
989 timeout=support.LOOPBACK_TIMEOUT)
Guido van Rossum04110fb2007-08-24 16:32:05 +0000990 smtp.quit()
991
992 def testEHLO(self):
Victor Stinner7772b1a2019-12-11 22:17:04 +0100993 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
994 timeout=support.LOOPBACK_TIMEOUT)
Guido van Rossum04110fb2007-08-24 16:32:05 +0000995
996 # no features should be present before the EHLO
997 self.assertEqual(smtp.esmtp_features, {})
998
999 # features expected from the test server
1000 expected_features = {'expn':'',
1001 'size': '20000000',
1002 'starttls': '',
1003 'deliverby': '',
1004 'help': '',
1005 }
1006
1007 smtp.ehlo()
1008 self.assertEqual(smtp.esmtp_features, expected_features)
1009 for k in expected_features:
1010 self.assertTrue(smtp.has_extn(k))
1011 self.assertFalse(smtp.has_extn('unsupported-feature'))
1012 smtp.quit()
1013
1014 def testVRFY(self):
Victor Stinner7772b1a2019-12-11 22:17:04 +01001015 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1016 timeout=support.LOOPBACK_TIMEOUT)
Guido van Rossum04110fb2007-08-24 16:32:05 +00001017
Barry Warsawc5ea7542015-07-09 10:39:55 -04001018 for addr_spec, name in sim_users.items():
Guido van Rossum04110fb2007-08-24 16:32:05 +00001019 expected_known = (250, bytes('%s %s' %
Barry Warsawc5ea7542015-07-09 10:39:55 -04001020 (name, smtplib.quoteaddr(addr_spec)),
Guido van Rossum5a23cc52007-08-30 14:02:43 +00001021 "ascii"))
Barry Warsawc5ea7542015-07-09 10:39:55 -04001022 self.assertEqual(smtp.vrfy(addr_spec), expected_known)
Guido van Rossum04110fb2007-08-24 16:32:05 +00001023
1024 u = 'nobody@nowhere.com'
R David Murray46346762011-07-18 21:38:54 -04001025 expected_unknown = (550, ('No such user: %s' % u).encode('ascii'))
Guido van Rossum04110fb2007-08-24 16:32:05 +00001026 self.assertEqual(smtp.vrfy(u), expected_unknown)
1027 smtp.quit()
1028
1029 def testEXPN(self):
Victor Stinner7772b1a2019-12-11 22:17:04 +01001030 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1031 timeout=support.LOOPBACK_TIMEOUT)
Guido van Rossum04110fb2007-08-24 16:32:05 +00001032
1033 for listname, members in sim_lists.items():
1034 users = []
1035 for m in members:
1036 users.append('%s %s' % (sim_users[m], smtplib.quoteaddr(m)))
Guido van Rossum5a23cc52007-08-30 14:02:43 +00001037 expected_known = (250, bytes('\n'.join(users), "ascii"))
Guido van Rossum04110fb2007-08-24 16:32:05 +00001038 self.assertEqual(smtp.expn(listname), expected_known)
1039
1040 u = 'PSU-Members-List'
1041 expected_unknown = (550, b'No access for you!')
1042 self.assertEqual(smtp.expn(u), expected_unknown)
1043 smtp.quit()
1044
R David Murray76e13c12014-07-03 14:47:46 -04001045 def testAUTH_PLAIN(self):
1046 self.serv.add_feature("AUTH PLAIN")
Victor Stinner7772b1a2019-12-11 22:17:04 +01001047 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1048 timeout=support.LOOPBACK_TIMEOUT)
R David Murrayb0deeb42015-11-08 01:03:52 -05001049 resp = smtp.login(sim_auth[0], sim_auth[1])
1050 self.assertEqual(resp, (235, b'Authentication Succeeded'))
R David Murray76e13c12014-07-03 14:47:46 -04001051 smtp.close()
1052
R. David Murrayfb123912009-05-28 18:19:00 +00001053 def testAUTH_LOGIN(self):
R. David Murrayfb123912009-05-28 18:19:00 +00001054 self.serv.add_feature("AUTH LOGIN")
Victor Stinner7772b1a2019-12-11 22:17:04 +01001055 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1056 timeout=support.LOOPBACK_TIMEOUT)
R David Murrayb0deeb42015-11-08 01:03:52 -05001057 resp = smtp.login(sim_auth[0], sim_auth[1])
1058 self.assertEqual(resp, (235, b'Authentication Succeeded'))
Benjamin Petersond094efd2010-10-31 17:15:42 +00001059 smtp.close()
R. David Murrayfb123912009-05-28 18:19:00 +00001060
Christian Heimesc64a1a62019-09-25 16:30:20 +02001061 @requires_hashdigest('md5')
R. David Murrayfb123912009-05-28 18:19:00 +00001062 def testAUTH_CRAM_MD5(self):
R. David Murrayfb123912009-05-28 18:19:00 +00001063 self.serv.add_feature("AUTH CRAM-MD5")
Victor Stinner7772b1a2019-12-11 22:17:04 +01001064 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1065 timeout=support.LOOPBACK_TIMEOUT)
R David Murrayb0deeb42015-11-08 01:03:52 -05001066 resp = smtp.login(sim_auth[0], sim_auth[1])
1067 self.assertEqual(resp, (235, b'Authentication Succeeded'))
Benjamin Petersond094efd2010-10-31 17:15:42 +00001068 smtp.close()
R. David Murrayfb123912009-05-28 18:19:00 +00001069
Andrew Kuchling78591822013-11-11 14:03:23 -05001070 def testAUTH_multiple(self):
1071 # Test that multiple authentication methods are tried.
1072 self.serv.add_feature("AUTH BOGUS PLAIN LOGIN CRAM-MD5")
Victor Stinner7772b1a2019-12-11 22:17:04 +01001073 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1074 timeout=support.LOOPBACK_TIMEOUT)
R David Murrayb0deeb42015-11-08 01:03:52 -05001075 resp = smtp.login(sim_auth[0], sim_auth[1])
1076 self.assertEqual(resp, (235, b'Authentication Succeeded'))
R David Murray76e13c12014-07-03 14:47:46 -04001077 smtp.close()
1078
1079 def test_auth_function(self):
Christian Heimesc64a1a62019-09-25 16:30:20 +02001080 supported = {'PLAIN', 'LOGIN'}
1081 try:
1082 hashlib.md5()
1083 except ValueError:
1084 pass
1085 else:
1086 supported.add('CRAM-MD5')
R David Murrayb0deeb42015-11-08 01:03:52 -05001087 for mechanism in supported:
1088 self.serv.add_feature("AUTH {}".format(mechanism))
1089 for mechanism in supported:
1090 with self.subTest(mechanism=mechanism):
1091 smtp = smtplib.SMTP(HOST, self.port,
Victor Stinner7772b1a2019-12-11 22:17:04 +01001092 local_hostname='localhost',
1093 timeout=support.LOOPBACK_TIMEOUT)
R David Murrayb0deeb42015-11-08 01:03:52 -05001094 smtp.ehlo('foo')
1095 smtp.user, smtp.password = sim_auth[0], sim_auth[1]
1096 method = 'auth_' + mechanism.lower().replace('-', '_')
1097 resp = smtp.auth(mechanism, getattr(smtp, method))
1098 self.assertEqual(resp, (235, b'Authentication Succeeded'))
1099 smtp.close()
Andrew Kuchling78591822013-11-11 14:03:23 -05001100
R David Murray0cff49f2014-08-30 16:51:59 -04001101 def test_quit_resets_greeting(self):
1102 smtp = smtplib.SMTP(HOST, self.port,
1103 local_hostname='localhost',
Victor Stinner7772b1a2019-12-11 22:17:04 +01001104 timeout=support.LOOPBACK_TIMEOUT)
R David Murray0cff49f2014-08-30 16:51:59 -04001105 code, message = smtp.ehlo()
1106 self.assertEqual(code, 250)
1107 self.assertIn('size', smtp.esmtp_features)
1108 smtp.quit()
1109 self.assertNotIn('size', smtp.esmtp_features)
1110 smtp.connect(HOST, self.port)
1111 self.assertNotIn('size', smtp.esmtp_features)
1112 smtp.ehlo_or_helo_if_needed()
1113 self.assertIn('size', smtp.esmtp_features)
1114 smtp.quit()
1115
Barry Warsaw1f5c9582011-03-15 15:04:44 -04001116 def test_with_statement(self):
1117 with smtplib.SMTP(HOST, self.port) as smtp:
1118 code, message = smtp.noop()
1119 self.assertEqual(code, 250)
1120 self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo')
1121 with smtplib.SMTP(HOST, self.port) as smtp:
1122 smtp.close()
1123 self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo')
1124
1125 def test_with_statement_QUIT_failure(self):
Barry Warsaw1f5c9582011-03-15 15:04:44 -04001126 with self.assertRaises(smtplib.SMTPResponseException) as error:
1127 with smtplib.SMTP(HOST, self.port) as smtp:
1128 smtp.noop()
R David Murray6bd52022013-03-21 00:32:31 -04001129 self.serv._SMTPchannel.quit_response = '421 QUIT FAILED'
Barry Warsaw1f5c9582011-03-15 15:04:44 -04001130 self.assertEqual(error.exception.smtp_code, 421)
1131 self.assertEqual(error.exception.smtp_error, b'QUIT FAILED')
Barry Warsaw1f5c9582011-03-15 15:04:44 -04001132
R. David Murrayfb123912009-05-28 18:19:00 +00001133 #TODO: add tests for correct AUTH method fallback now that the
1134 #test infrastructure can support it.
1135
R David Murrayafb151a2014-04-14 18:21:38 -04001136 # Issue 17498: make sure _rset does not raise SMTPServerDisconnected exception
1137 def test__rest_from_mail_cmd(self):
Victor Stinner7772b1a2019-12-11 22:17:04 +01001138 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1139 timeout=support.LOOPBACK_TIMEOUT)
R David Murrayafb151a2014-04-14 18:21:38 -04001140 smtp.noop()
1141 self.serv._SMTPchannel.mail_response = '451 Requested action aborted'
1142 self.serv._SMTPchannel.disconnect = True
1143 with self.assertRaises(smtplib.SMTPSenderRefused):
1144 smtp.sendmail('John', 'Sally', 'test message')
1145 self.assertIsNone(smtp.sock)
1146
R David Murrayd312c742013-03-20 20:36:14 -04001147 # Issue 5713: make sure close, not rset, is called if we get a 421 error
1148 def test_421_from_mail_cmd(self):
Victor Stinner7772b1a2019-12-11 22:17:04 +01001149 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1150 timeout=support.LOOPBACK_TIMEOUT)
R David Murray853c0f92013-03-20 21:54:05 -04001151 smtp.noop()
R David Murrayd312c742013-03-20 20:36:14 -04001152 self.serv._SMTPchannel.mail_response = '421 closing connection'
1153 with self.assertRaises(smtplib.SMTPSenderRefused):
1154 smtp.sendmail('John', 'Sally', 'test message')
1155 self.assertIsNone(smtp.sock)
R David Murray03b01162013-03-20 22:11:40 -04001156 self.assertEqual(self.serv._SMTPchannel.rset_count, 0)
R David Murrayd312c742013-03-20 20:36:14 -04001157
1158 def test_421_from_rcpt_cmd(self):
Victor Stinner7772b1a2019-12-11 22:17:04 +01001159 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1160 timeout=support.LOOPBACK_TIMEOUT)
R David Murray853c0f92013-03-20 21:54:05 -04001161 smtp.noop()
R David Murrayd312c742013-03-20 20:36:14 -04001162 self.serv._SMTPchannel.rcpt_response = ['250 accepted', '421 closing']
1163 with self.assertRaises(smtplib.SMTPRecipientsRefused) as r:
1164 smtp.sendmail('John', ['Sally', 'Frank', 'George'], 'test message')
1165 self.assertIsNone(smtp.sock)
1166 self.assertEqual(self.serv._SMTPchannel.rset_count, 0)
1167 self.assertDictEqual(r.exception.args[0], {'Frank': (421, b'closing')})
1168
1169 def test_421_from_data_cmd(self):
1170 class MySimSMTPChannel(SimSMTPChannel):
1171 def found_terminator(self):
1172 if self.smtp_state == self.DATA:
1173 self.push('421 closing')
1174 else:
1175 super().found_terminator()
1176 self.serv.channel_class = MySimSMTPChannel
Victor Stinner7772b1a2019-12-11 22:17:04 +01001177 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1178 timeout=support.LOOPBACK_TIMEOUT)
R David Murray853c0f92013-03-20 21:54:05 -04001179 smtp.noop()
R David Murrayd312c742013-03-20 20:36:14 -04001180 with self.assertRaises(smtplib.SMTPDataError):
1181 smtp.sendmail('John@foo.org', ['Sally@foo.org'], 'test message')
1182 self.assertIsNone(smtp.sock)
1183 self.assertEqual(self.serv._SMTPchannel.rcpt_count, 0)
1184
R David Murraycee7cf62015-05-16 13:58:14 -04001185 def test_smtputf8_NotSupportedError_if_no_server_support(self):
1186 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001187 HOST, self.port, local_hostname='localhost',
1188 timeout=support.LOOPBACK_TIMEOUT)
R David Murraycee7cf62015-05-16 13:58:14 -04001189 self.addCleanup(smtp.close)
1190 smtp.ehlo()
1191 self.assertTrue(smtp.does_esmtp)
1192 self.assertFalse(smtp.has_extn('smtputf8'))
1193 self.assertRaises(
1194 smtplib.SMTPNotSupportedError,
1195 smtp.sendmail,
1196 'John', 'Sally', '', mail_options=['BODY=8BITMIME', 'SMTPUTF8'])
1197 self.assertRaises(
1198 smtplib.SMTPNotSupportedError,
1199 smtp.mail, 'John', options=['BODY=8BITMIME', 'SMTPUTF8'])
1200
1201 def test_send_unicode_without_SMTPUTF8(self):
1202 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001203 HOST, self.port, local_hostname='localhost',
1204 timeout=support.LOOPBACK_TIMEOUT)
R David Murraycee7cf62015-05-16 13:58:14 -04001205 self.addCleanup(smtp.close)
1206 self.assertRaises(UnicodeEncodeError, smtp.sendmail, 'Alice', 'Böb', '')
1207 self.assertRaises(UnicodeEncodeError, smtp.mail, 'Älice')
1208
chason48ed88a2018-07-26 04:01:28 +09001209 def test_send_message_error_on_non_ascii_addrs_if_no_smtputf8(self):
1210 # This test is located here and not in the SMTPUTF8SimTests
1211 # class because it needs a "regular" SMTP server to work
1212 msg = EmailMessage()
1213 msg['From'] = "Páolo <főo@bar.com>"
1214 msg['To'] = 'Dinsdale'
1215 msg['Subject'] = 'Nudge nudge, wink, wink \u1F609'
1216 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001217 HOST, self.port, local_hostname='localhost',
1218 timeout=support.LOOPBACK_TIMEOUT)
chason48ed88a2018-07-26 04:01:28 +09001219 self.addCleanup(smtp.close)
1220 with self.assertRaises(smtplib.SMTPNotSupportedError):
1221 smtp.send_message(msg)
1222
Stéphane Wirtel8d83e4b2018-01-31 01:02:51 +01001223 def test_name_field_not_included_in_envelop_addresses(self):
1224 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001225 HOST, self.port, local_hostname='localhost',
1226 timeout=support.LOOPBACK_TIMEOUT)
Stéphane Wirtel8d83e4b2018-01-31 01:02:51 +01001227 self.addCleanup(smtp.close)
1228
1229 message = EmailMessage()
1230 message['From'] = email.utils.formataddr(('Michaël', 'michael@example.com'))
1231 message['To'] = email.utils.formataddr(('René', 'rene@example.com'))
1232
1233 self.assertDictEqual(smtp.send_message(message), {})
1234
1235 self.assertEqual(self.serv._addresses['from'], 'michael@example.com')
1236 self.assertEqual(self.serv._addresses['tos'], ['rene@example.com'])
1237
R David Murraycee7cf62015-05-16 13:58:14 -04001238
1239class SimSMTPUTF8Server(SimSMTPServer):
1240
1241 def __init__(self, *args, **kw):
1242 # The base SMTP server turns these on automatically, but our test
1243 # server is set up to munge the EHLO response, so we need to provide
1244 # them as well. And yes, the call is to SMTPServer not SimSMTPServer.
1245 self._extra_features = ['SMTPUTF8', '8BITMIME']
1246 smtpd.SMTPServer.__init__(self, *args, **kw)
1247
1248 def handle_accepted(self, conn, addr):
1249 self._SMTPchannel = self.channel_class(
1250 self._extra_features, self, conn, addr,
1251 decode_data=self._decode_data,
1252 enable_SMTPUTF8=self.enable_SMTPUTF8,
1253 )
1254
1255 def process_message(self, peer, mailfrom, rcpttos, data, mail_options=None,
1256 rcpt_options=None):
1257 self.last_peer = peer
1258 self.last_mailfrom = mailfrom
1259 self.last_rcpttos = rcpttos
1260 self.last_message = data
1261 self.last_mail_options = mail_options
1262 self.last_rcpt_options = rcpt_options
1263
1264
R David Murraycee7cf62015-05-16 13:58:14 -04001265class SMTPUTF8SimTests(unittest.TestCase):
1266
R David Murray83084442015-05-17 19:27:22 -04001267 maxDiff = None
1268
R David Murraycee7cf62015-05-16 13:58:14 -04001269 def setUp(self):
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +01001270 self.thread_key = threading_setup()
R David Murraycee7cf62015-05-16 13:58:14 -04001271 self.real_getfqdn = socket.getfqdn
1272 socket.getfqdn = mock_socket.getfqdn
1273 self.serv_evt = threading.Event()
1274 self.client_evt = threading.Event()
1275 # Pick a random unused port by passing 0 for the port number
1276 self.serv = SimSMTPUTF8Server((HOST, 0), ('nowhere', -1),
1277 decode_data=False,
1278 enable_SMTPUTF8=True)
1279 # Keep a note of what port was assigned
1280 self.port = self.serv.socket.getsockname()[1]
1281 serv_args = (self.serv, self.serv_evt, self.client_evt)
1282 self.thread = threading.Thread(target=debugging_server, args=serv_args)
1283 self.thread.start()
1284
1285 # wait until server thread has assigned a port number
1286 self.serv_evt.wait()
1287 self.serv_evt.clear()
1288
1289 def tearDown(self):
1290 socket.getfqdn = self.real_getfqdn
1291 # indicate that the client is finished
1292 self.client_evt.set()
1293 # wait for the server thread to terminate
1294 self.serv_evt.wait()
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +01001295 join_thread(self.thread)
1296 del self.thread
1297 self.doCleanups()
1298 threading_cleanup(*self.thread_key)
R David Murraycee7cf62015-05-16 13:58:14 -04001299
1300 def test_test_server_supports_extensions(self):
1301 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001302 HOST, self.port, local_hostname='localhost',
1303 timeout=support.LOOPBACK_TIMEOUT)
R David Murraycee7cf62015-05-16 13:58:14 -04001304 self.addCleanup(smtp.close)
1305 smtp.ehlo()
1306 self.assertTrue(smtp.does_esmtp)
1307 self.assertTrue(smtp.has_extn('smtputf8'))
1308
1309 def test_send_unicode_with_SMTPUTF8_via_sendmail(self):
1310 m = '¡a test message containing unicode!'.encode('utf-8')
1311 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001312 HOST, self.port, local_hostname='localhost',
1313 timeout=support.LOOPBACK_TIMEOUT)
R David Murraycee7cf62015-05-16 13:58:14 -04001314 self.addCleanup(smtp.close)
1315 smtp.sendmail('Jőhn', 'Sálly', m,
1316 mail_options=['BODY=8BITMIME', 'SMTPUTF8'])
1317 self.assertEqual(self.serv.last_mailfrom, 'Jőhn')
1318 self.assertEqual(self.serv.last_rcpttos, ['Sálly'])
1319 self.assertEqual(self.serv.last_message, m)
1320 self.assertIn('BODY=8BITMIME', self.serv.last_mail_options)
1321 self.assertIn('SMTPUTF8', self.serv.last_mail_options)
1322 self.assertEqual(self.serv.last_rcpt_options, [])
1323
1324 def test_send_unicode_with_SMTPUTF8_via_low_level_API(self):
1325 m = '¡a test message containing unicode!'.encode('utf-8')
1326 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001327 HOST, self.port, local_hostname='localhost',
1328 timeout=support.LOOPBACK_TIMEOUT)
R David Murraycee7cf62015-05-16 13:58:14 -04001329 self.addCleanup(smtp.close)
1330 smtp.ehlo()
1331 self.assertEqual(
1332 smtp.mail('Jő', options=['BODY=8BITMIME', 'SMTPUTF8']),
1333 (250, b'OK'))
1334 self.assertEqual(smtp.rcpt('János'), (250, b'OK'))
1335 self.assertEqual(smtp.data(m), (250, b'OK'))
1336 self.assertEqual(self.serv.last_mailfrom, 'Jő')
1337 self.assertEqual(self.serv.last_rcpttos, ['János'])
1338 self.assertEqual(self.serv.last_message, m)
1339 self.assertIn('BODY=8BITMIME', self.serv.last_mail_options)
1340 self.assertIn('SMTPUTF8', self.serv.last_mail_options)
1341 self.assertEqual(self.serv.last_rcpt_options, [])
1342
R David Murray83084442015-05-17 19:27:22 -04001343 def test_send_message_uses_smtputf8_if_addrs_non_ascii(self):
1344 msg = EmailMessage()
1345 msg['From'] = "Páolo <főo@bar.com>"
1346 msg['To'] = 'Dinsdale'
1347 msg['Subject'] = 'Nudge nudge, wink, wink \u1F609'
1348 # XXX I don't know why I need two \n's here, but this is an existing
1349 # bug (if it is one) and not a problem with the new functionality.
1350 msg.set_content("oh là là, know what I mean, know what I mean?\n\n")
1351 # XXX smtpd converts received /r/n to /n, so we can't easily test that
1352 # we are successfully sending /r/n :(.
1353 expected = textwrap.dedent("""\
1354 From: Páolo <főo@bar.com>
1355 To: Dinsdale
1356 Subject: Nudge nudge, wink, wink \u1F609
1357 Content-Type: text/plain; charset="utf-8"
1358 Content-Transfer-Encoding: 8bit
1359 MIME-Version: 1.0
1360
1361 oh là là, know what I mean, know what I mean?
1362 """)
1363 smtp = smtplib.SMTP(
Victor Stinner07871b22019-12-10 20:32:59 +01001364 HOST, self.port, local_hostname='localhost',
1365 timeout=support.LOOPBACK_TIMEOUT)
R David Murray83084442015-05-17 19:27:22 -04001366 self.addCleanup(smtp.close)
1367 self.assertEqual(smtp.send_message(msg), {})
1368 self.assertEqual(self.serv.last_mailfrom, 'főo@bar.com')
1369 self.assertEqual(self.serv.last_rcpttos, ['Dinsdale'])
1370 self.assertEqual(self.serv.last_message.decode(), expected)
1371 self.assertIn('BODY=8BITMIME', self.serv.last_mail_options)
1372 self.assertIn('SMTPUTF8', self.serv.last_mail_options)
1373 self.assertEqual(self.serv.last_rcpt_options, [])
1374
Guido van Rossum04110fb2007-08-24 16:32:05 +00001375
Barry Warsawc5ea7542015-07-09 10:39:55 -04001376EXPECTED_RESPONSE = encode_base64(b'\0psu\0doesnotexist', eol='')
1377
1378class SimSMTPAUTHInitialResponseChannel(SimSMTPChannel):
1379 def smtp_AUTH(self, arg):
1380 # RFC 4954's AUTH command allows for an optional initial-response.
1381 # Not all AUTH methods support this; some require a challenge. AUTH
1382 # PLAIN does those, so test that here. See issue #15014.
1383 args = arg.split()
1384 if args[0].lower() == 'plain':
1385 if len(args) == 2:
1386 # AUTH PLAIN <initial-response> with the response base 64
1387 # encoded. Hard code the expected response for the test.
1388 if args[1] == EXPECTED_RESPONSE:
1389 self.push('235 Ok')
1390 return
1391 self.push('571 Bad authentication')
1392
1393class SimSMTPAUTHInitialResponseServer(SimSMTPServer):
1394 channel_class = SimSMTPAUTHInitialResponseChannel
1395
1396
Barry Warsawc5ea7542015-07-09 10:39:55 -04001397class SMTPAUTHInitialResponseSimTests(unittest.TestCase):
1398 def setUp(self):
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +01001399 self.thread_key = threading_setup()
Barry Warsawc5ea7542015-07-09 10:39:55 -04001400 self.real_getfqdn = socket.getfqdn
1401 socket.getfqdn = mock_socket.getfqdn
1402 self.serv_evt = threading.Event()
1403 self.client_evt = threading.Event()
1404 # Pick a random unused port by passing 0 for the port number
1405 self.serv = SimSMTPAUTHInitialResponseServer(
1406 (HOST, 0), ('nowhere', -1), decode_data=True)
1407 # Keep a note of what port was assigned
1408 self.port = self.serv.socket.getsockname()[1]
1409 serv_args = (self.serv, self.serv_evt, self.client_evt)
1410 self.thread = threading.Thread(target=debugging_server, args=serv_args)
1411 self.thread.start()
1412
1413 # wait until server thread has assigned a port number
1414 self.serv_evt.wait()
1415 self.serv_evt.clear()
1416
1417 def tearDown(self):
1418 socket.getfqdn = self.real_getfqdn
1419 # indicate that the client is finished
1420 self.client_evt.set()
1421 # wait for the server thread to terminate
1422 self.serv_evt.wait()
Pablo Galindo5b7a2cb2018-09-08 00:15:22 +01001423 join_thread(self.thread)
1424 del self.thread
1425 self.doCleanups()
1426 threading_cleanup(*self.thread_key)
Barry Warsawc5ea7542015-07-09 10:39:55 -04001427
1428 def testAUTH_PLAIN_initial_response_login(self):
1429 self.serv.add_feature('AUTH PLAIN')
Victor Stinner7772b1a2019-12-11 22:17:04 +01001430 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1431 timeout=support.LOOPBACK_TIMEOUT)
Barry Warsawc5ea7542015-07-09 10:39:55 -04001432 smtp.login('psu', 'doesnotexist')
1433 smtp.close()
1434
1435 def testAUTH_PLAIN_initial_response_auth(self):
1436 self.serv.add_feature('AUTH PLAIN')
Victor Stinner7772b1a2019-12-11 22:17:04 +01001437 smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost',
1438 timeout=support.LOOPBACK_TIMEOUT)
Barry Warsawc5ea7542015-07-09 10:39:55 -04001439 smtp.user = 'psu'
1440 smtp.password = 'doesnotexist'
1441 code, response = smtp.auth('plain', smtp.auth_plain)
1442 smtp.close()
1443 self.assertEqual(code, 235)
1444
1445
Guido van Rossumd8faa362007-04-27 19:54:29 +00001446if __name__ == '__main__':
chason48ed88a2018-07-26 04:01:28 +09001447 unittest.main()