blob: caeb79763907e110038393a8463257dec28ab911 [file] [log] [blame]
R David Murrayd1a30c92012-05-26 14:33:59 -04001import unittest
Richard Jones64b02de2010-08-03 06:39:33 +00002from test import support, mock_socket
Richard Jones8cb36192010-07-23 16:20:40 +00003import socket
Richard Jones8cb36192010-07-23 16:20:40 +00004import io
5import smtpd
Richard Jones64b02de2010-08-03 06:39:33 +00006import asyncore
Richard Jones8cb36192010-07-23 16:20:40 +00007
Richard Jones8cb36192010-07-23 16:20:40 +00008
9class DummyServer(smtpd.SMTPServer):
R David Murray554bcbf2014-06-11 11:18:08 -040010 def __init__(self, localaddr, remoteaddr, decode_data=True):
11 smtpd.SMTPServer.__init__(self, localaddr, remoteaddr,
12 decode_data=decode_data)
Georg Brandl6d23c442010-07-29 13:19:42 +000013 self.messages = []
R David Murray554bcbf2014-06-11 11:18:08 -040014 if decode_data:
15 self.return_status = 'return status'
16 else:
17 self.return_status = b'return status'
Richard Jones64b02de2010-08-03 06:39:33 +000018
Richard Jones8cb36192010-07-23 16:20:40 +000019 def process_message(self, peer, mailfrom, rcpttos, data):
20 self.messages.append((peer, mailfrom, rcpttos, data))
R David Murray554bcbf2014-06-11 11:18:08 -040021 if data == self.return_status:
Richard Jones8cb36192010-07-23 16:20:40 +000022 return '250 Okish'
23
Richard Jones4aa0d4d2010-08-04 01:20:14 +000024
Richard Jones8cb36192010-07-23 16:20:40 +000025class DummyDispatcherBroken(Exception):
26 pass
27
Richard Jones4aa0d4d2010-08-04 01:20:14 +000028
Richard Jones8cb36192010-07-23 16:20:40 +000029class BrokenDummyServer(DummyServer):
30 def listen(self, num):
31 raise DummyDispatcherBroken()
32
Richard Jones4aa0d4d2010-08-04 01:20:14 +000033
R David Murrayd1a30c92012-05-26 14:33:59 -040034class SMTPDServerTest(unittest.TestCase):
Richard Jones4aa0d4d2010-08-04 01:20:14 +000035 def setUp(self):
36 smtpd.socket = asyncore.socket = mock_socket
37
38 def test_process_message_unimplemented(self):
R David Murray6fe56a32014-06-11 13:48:58 -040039 server = smtpd.SMTPServer((support.HOST, 0), ('b', 0),
40 decode_data=True)
Richard Jones4aa0d4d2010-08-04 01:20:14 +000041 conn, addr = server.accept()
R David Murray554bcbf2014-06-11 11:18:08 -040042 channel = smtpd.SMTPChannel(server, conn, addr, decode_data=True)
Richard Jones4aa0d4d2010-08-04 01:20:14 +000043
44 def write_line(line):
45 channel.socket.queue_recv(line)
46 channel.handle_read()
47
R David Murrayd1a30c92012-05-26 14:33:59 -040048 write_line(b'HELO example')
Richard Jones4aa0d4d2010-08-04 01:20:14 +000049 write_line(b'MAIL From:eggs@example')
50 write_line(b'RCPT To:spam@example')
51 write_line(b'DATA')
52 self.assertRaises(NotImplementedError, write_line, b'spam\r\n.\r\n')
53
R David Murray554bcbf2014-06-11 11:18:08 -040054 def test_decode_data_default_warns(self):
55 with self.assertWarns(DeprecationWarning):
R David Murray6fe56a32014-06-11 13:48:58 -040056 smtpd.SMTPServer((support.HOST, 0), ('b', 0))
R David Murray554bcbf2014-06-11 11:18:08 -040057
Richard Jones4aa0d4d2010-08-04 01:20:14 +000058 def tearDown(self):
Richard Jonesdaf23502010-08-16 01:48:14 +000059 asyncore.close_all()
Richard Jones4aa0d4d2010-08-04 01:20:14 +000060 asyncore.socket = smtpd.socket = socket
61
62
R David Murray6fe56a32014-06-11 13:48:58 -040063class TestFamilyDetection(unittest.TestCase):
64 def setUp(self):
65 smtpd.socket = asyncore.socket = mock_socket
66
67 def tearDown(self):
68 asyncore.close_all()
69 asyncore.socket = smtpd.socket = socket
70
71 @unittest.skipUnless(support.IPV6_ENABLED, "IPv6 not enabled")
72 def test_socket_uses_IPv6(self):
73 server = smtpd.SMTPServer((support.HOSTv6, 0), (support.HOST, 0),
74 decode_data=False)
75 self.assertEqual(server.socket.family, socket.AF_INET6)
76
77 def test_socket_uses_IPv4(self):
78 server = smtpd.SMTPServer((support.HOST, 0), (support.HOSTv6, 0),
79 decode_data=False)
80 self.assertEqual(server.socket.family, socket.AF_INET)
81
82
R David Murrayd1a30c92012-05-26 14:33:59 -040083class SMTPDChannelTest(unittest.TestCase):
Richard Jones8cb36192010-07-23 16:20:40 +000084 def setUp(self):
Richard Jones64b02de2010-08-03 06:39:33 +000085 smtpd.socket = asyncore.socket = mock_socket
Antoine Pitroue0815e22011-11-12 20:36:29 +010086 self.old_debugstream = smtpd.DEBUGSTREAM
Richard Jones8cb36192010-07-23 16:20:40 +000087 self.debug = smtpd.DEBUGSTREAM = io.StringIO()
R David Murray6fe56a32014-06-11 13:48:58 -040088 self.server = DummyServer((support.HOST, 0), ('b', 0))
Richard Jones8cb36192010-07-23 16:20:40 +000089 conn, addr = self.server.accept()
R David Murray554bcbf2014-06-11 11:18:08 -040090 self.channel = smtpd.SMTPChannel(self.server, conn, addr,
91 decode_data=True)
Richard Jones8cb36192010-07-23 16:20:40 +000092
Richard Jones64b02de2010-08-03 06:39:33 +000093 def tearDown(self):
Richard Jonesdaf23502010-08-16 01:48:14 +000094 asyncore.close_all()
Richard Jones64b02de2010-08-03 06:39:33 +000095 asyncore.socket = smtpd.socket = socket
Antoine Pitroue0815e22011-11-12 20:36:29 +010096 smtpd.DEBUGSTREAM = self.old_debugstream
Richard Jones64b02de2010-08-03 06:39:33 +000097
Richard Jones8cb36192010-07-23 16:20:40 +000098 def write_line(self, line):
99 self.channel.socket.queue_recv(line)
100 self.channel.handle_read()
101
102 def test_broken_connect(self):
R David Murray6fe56a32014-06-11 13:48:58 -0400103 self.assertRaises(
104 DummyDispatcherBroken, BrokenDummyServer,
105 (support.HOST, 0), ('b', 0))
Richard Jones8cb36192010-07-23 16:20:40 +0000106
107 def test_server_accept(self):
108 self.server.handle_accept()
109
110 def test_missing_data(self):
111 self.write_line(b'')
Georg Brandl17e3d692010-07-31 10:08:09 +0000112 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000113 b'500 Error: bad syntax\r\n')
114
R David Murrayd1a30c92012-05-26 14:33:59 -0400115 def test_EHLO(self):
116 self.write_line(b'EHLO example')
117 self.assertEqual(self.channel.socket.last, b'250 HELP\r\n')
118
119 def test_EHLO_bad_syntax(self):
120 self.write_line(b'EHLO')
Georg Brandl17e3d692010-07-31 10:08:09 +0000121 self.assertEqual(self.channel.socket.last,
R David Murrayd1a30c92012-05-26 14:33:59 -0400122 b'501 Syntax: EHLO hostname\r\n')
123
124 def test_EHLO_duplicate(self):
125 self.write_line(b'EHLO example')
126 self.write_line(b'EHLO example')
127 self.assertEqual(self.channel.socket.last,
128 b'503 Duplicate HELO/EHLO\r\n')
129
130 def test_EHLO_HELO_duplicate(self):
131 self.write_line(b'EHLO example')
132 self.write_line(b'HELO example')
133 self.assertEqual(self.channel.socket.last,
134 b'503 Duplicate HELO/EHLO\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000135
136 def test_HELO(self):
Richard Jones64b02de2010-08-03 06:39:33 +0000137 name = smtpd.socket.getfqdn()
R David Murrayd1a30c92012-05-26 14:33:59 -0400138 self.write_line(b'HELO example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000139 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000140 '250 {}\r\n'.format(name).encode('ascii'))
141
R David Murrayd1a30c92012-05-26 14:33:59 -0400142 def test_HELO_EHLO_duplicate(self):
143 self.write_line(b'HELO example')
144 self.write_line(b'EHLO example')
145 self.assertEqual(self.channel.socket.last,
146 b'503 Duplicate HELO/EHLO\r\n')
147
148 def test_HELP(self):
149 self.write_line(b'HELP')
150 self.assertEqual(self.channel.socket.last,
151 b'250 Supported commands: EHLO HELO MAIL RCPT ' + \
152 b'DATA RSET NOOP QUIT VRFY\r\n')
153
154 def test_HELP_command(self):
155 self.write_line(b'HELP MAIL')
156 self.assertEqual(self.channel.socket.last,
157 b'250 Syntax: MAIL FROM: <address>\r\n')
158
159 def test_HELP_command_unknown(self):
160 self.write_line(b'HELP SPAM')
161 self.assertEqual(self.channel.socket.last,
162 b'501 Supported commands: EHLO HELO MAIL RCPT ' + \
163 b'DATA RSET NOOP QUIT VRFY\r\n')
164
Richard Jones8cb36192010-07-23 16:20:40 +0000165 def test_HELO_bad_syntax(self):
166 self.write_line(b'HELO')
Georg Brandl17e3d692010-07-31 10:08:09 +0000167 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000168 b'501 Syntax: HELO hostname\r\n')
169
170 def test_HELO_duplicate(self):
R David Murrayd1a30c92012-05-26 14:33:59 -0400171 self.write_line(b'HELO example')
172 self.write_line(b'HELO example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000173 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000174 b'503 Duplicate HELO/EHLO\r\n')
175
R David Murrayd1a30c92012-05-26 14:33:59 -0400176 def test_HELO_parameter_rejected_when_extensions_not_enabled(self):
177 self.extended_smtp = False
178 self.write_line(b'HELO example')
179 self.write_line(b'MAIL from:<foo@example.com> SIZE=1234')
180 self.assertEqual(self.channel.socket.last,
181 b'501 Syntax: MAIL FROM: <address>\r\n')
182
183 def test_MAIL_allows_space_after_colon(self):
184 self.write_line(b'HELO example')
185 self.write_line(b'MAIL from: <foo@example.com>')
186 self.assertEqual(self.channel.socket.last,
187 b'250 OK\r\n')
188
189 def test_extended_MAIL_allows_space_after_colon(self):
190 self.write_line(b'EHLO example')
191 self.write_line(b'MAIL from: <foo@example.com> size=20')
192 self.assertEqual(self.channel.socket.last,
193 b'250 OK\r\n')
194
Richard Jones8cb36192010-07-23 16:20:40 +0000195 def test_NOOP(self):
196 self.write_line(b'NOOP')
R David Murrayd1a30c92012-05-26 14:33:59 -0400197 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000198
R David Murray669b7552012-03-20 16:16:29 -0400199 def test_HELO_NOOP(self):
200 self.write_line(b'HELO example')
201 self.write_line(b'NOOP')
R David Murrayd1a30c92012-05-26 14:33:59 -0400202 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
R David Murray669b7552012-03-20 16:16:29 -0400203
Richard Jones8cb36192010-07-23 16:20:40 +0000204 def test_NOOP_bad_syntax(self):
205 self.write_line(b'NOOP hi')
Georg Brandl17e3d692010-07-31 10:08:09 +0000206 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000207 b'501 Syntax: NOOP\r\n')
208
209 def test_QUIT(self):
210 self.write_line(b'QUIT')
Georg Brandl17e3d692010-07-31 10:08:09 +0000211 self.assertEqual(self.channel.socket.last, b'221 Bye\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000212
R David Murray669b7552012-03-20 16:16:29 -0400213 def test_HELO_QUIT(self):
214 self.write_line(b'HELO example')
215 self.write_line(b'QUIT')
216 self.assertEqual(self.channel.socket.last, b'221 Bye\r\n')
217
Richard Jones8cb36192010-07-23 16:20:40 +0000218 def test_QUIT_arg_ignored(self):
219 self.write_line(b'QUIT bye bye')
Georg Brandl17e3d692010-07-31 10:08:09 +0000220 self.assertEqual(self.channel.socket.last, b'221 Bye\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000221
222 def test_bad_state(self):
Georg Brandl17e3d692010-07-31 10:08:09 +0000223 self.channel.smtp_state = 'BAD STATE'
R David Murray669b7552012-03-20 16:16:29 -0400224 self.write_line(b'HELO example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000225 self.assertEqual(self.channel.socket.last,
226 b'451 Internal confusion\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000227
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000228 def test_command_too_long(self):
R David Murray669b7552012-03-20 16:16:29 -0400229 self.write_line(b'HELO example')
R David Murrayd1a30c92012-05-26 14:33:59 -0400230 self.write_line(b'MAIL from: ' +
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000231 b'a' * self.channel.command_size_limit +
232 b'@example')
233 self.assertEqual(self.channel.socket.last,
234 b'500 Error: line too long\r\n')
235
R David Murrayd1a30c92012-05-26 14:33:59 -0400236 def test_MAIL_command_limit_extended_with_SIZE(self):
237 self.write_line(b'EHLO example')
238 fill_len = self.channel.command_size_limit - len('MAIL from:<@example>')
239 self.write_line(b'MAIL from:<' +
240 b'a' * fill_len +
241 b'@example> SIZE=1234')
242 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
243
244 self.write_line(b'MAIL from:<' +
245 b'a' * (fill_len + 26) +
246 b'@example> SIZE=1234')
247 self.assertEqual(self.channel.socket.last,
248 b'500 Error: line too long\r\n')
249
250 def test_data_longer_than_default_data_size_limit(self):
251 # Hack the default so we don't have to generate so much data.
252 self.channel.data_size_limit = 1048
R David Murray669b7552012-03-20 16:16:29 -0400253 self.write_line(b'HELO example')
Georg Brandl1e5c5f82010-12-03 07:38:22 +0000254 self.write_line(b'MAIL From:eggs@example')
255 self.write_line(b'RCPT To:spam@example')
256 self.write_line(b'DATA')
257 self.write_line(b'A' * self.channel.data_size_limit +
258 b'A\r\n.')
259 self.assertEqual(self.channel.socket.last,
260 b'552 Error: Too much mail data\r\n')
261
R David Murrayd1a30c92012-05-26 14:33:59 -0400262 def test_MAIL_size_parameter(self):
263 self.write_line(b'EHLO example')
264 self.write_line(b'MAIL FROM:<eggs@example> SIZE=512')
265 self.assertEqual(self.channel.socket.last,
266 b'250 OK\r\n')
267
268 def test_MAIL_invalid_size_parameter(self):
269 self.write_line(b'EHLO example')
270 self.write_line(b'MAIL FROM:<eggs@example> SIZE=invalid')
271 self.assertEqual(self.channel.socket.last,
272 b'501 Syntax: MAIL FROM: <address> [SP <mail-parameters>]\r\n')
273
274 def test_MAIL_RCPT_unknown_parameters(self):
275 self.write_line(b'EHLO example')
276 self.write_line(b'MAIL FROM:<eggs@example> ham=green')
277 self.assertEqual(self.channel.socket.last,
278 b'555 MAIL FROM parameters not recognized or not implemented\r\n')
279
280 self.write_line(b'MAIL FROM:<eggs@example>')
281 self.write_line(b'RCPT TO:<eggs@example> ham=green')
282 self.assertEqual(self.channel.socket.last,
283 b'555 RCPT TO parameters not recognized or not implemented\r\n')
284
285 def test_MAIL_size_parameter_larger_than_default_data_size_limit(self):
286 self.channel.data_size_limit = 1048
287 self.write_line(b'EHLO example')
288 self.write_line(b'MAIL FROM:<eggs@example> SIZE=2096')
289 self.assertEqual(self.channel.socket.last,
290 b'552 Error: message size exceeds fixed maximum message size\r\n')
291
Richard Jones8cb36192010-07-23 16:20:40 +0000292 def test_need_MAIL(self):
R David Murray669b7552012-03-20 16:16:29 -0400293 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000294 self.write_line(b'RCPT to:spam@example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000295 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000296 b'503 Error: need MAIL command\r\n')
297
R David Murrayd1a30c92012-05-26 14:33:59 -0400298 def test_MAIL_syntax_HELO(self):
R David Murray669b7552012-03-20 16:16:29 -0400299 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000300 self.write_line(b'MAIL from eggs@example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000301 self.assertEqual(self.channel.socket.last,
R David Murrayd1a30c92012-05-26 14:33:59 -0400302 b'501 Syntax: MAIL FROM: <address>\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000303
R David Murrayd1a30c92012-05-26 14:33:59 -0400304 def test_MAIL_syntax_EHLO(self):
305 self.write_line(b'EHLO example')
306 self.write_line(b'MAIL from eggs@example')
307 self.assertEqual(self.channel.socket.last,
308 b'501 Syntax: MAIL FROM: <address> [SP <mail-parameters>]\r\n')
309
310 def test_MAIL_missing_address(self):
R David Murray669b7552012-03-20 16:16:29 -0400311 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000312 self.write_line(b'MAIL from:')
Georg Brandl17e3d692010-07-31 10:08:09 +0000313 self.assertEqual(self.channel.socket.last,
R David Murrayd1a30c92012-05-26 14:33:59 -0400314 b'501 Syntax: MAIL FROM: <address>\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000315
316 def test_MAIL_chevrons(self):
R David Murray669b7552012-03-20 16:16:29 -0400317 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000318 self.write_line(b'MAIL from:<eggs@example>')
R David Murrayd1a30c92012-05-26 14:33:59 -0400319 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
320
321 def test_MAIL_empty_chevrons(self):
322 self.write_line(b'EHLO example')
323 self.write_line(b'MAIL from:<>')
324 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
325
326 def test_MAIL_quoted_localpart(self):
327 self.write_line(b'EHLO example')
328 self.write_line(b'MAIL from: <"Fred Blogs"@example.com>')
329 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
330 self.assertEqual(self.channel.mailfrom, '"Fred Blogs"@example.com')
331
332 def test_MAIL_quoted_localpart_no_angles(self):
333 self.write_line(b'EHLO example')
334 self.write_line(b'MAIL from: "Fred Blogs"@example.com')
335 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
336 self.assertEqual(self.channel.mailfrom, '"Fred Blogs"@example.com')
337
338 def test_MAIL_quoted_localpart_with_size(self):
339 self.write_line(b'EHLO example')
340 self.write_line(b'MAIL from: <"Fred Blogs"@example.com> SIZE=1000')
341 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
342 self.assertEqual(self.channel.mailfrom, '"Fred Blogs"@example.com')
343
344 def test_MAIL_quoted_localpart_with_size_no_angles(self):
345 self.write_line(b'EHLO example')
346 self.write_line(b'MAIL from: "Fred Blogs"@example.com SIZE=1000')
347 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
348 self.assertEqual(self.channel.mailfrom, '"Fred Blogs"@example.com')
Richard Jones8cb36192010-07-23 16:20:40 +0000349
350 def test_nested_MAIL(self):
R David Murray669b7552012-03-20 16:16:29 -0400351 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000352 self.write_line(b'MAIL from:eggs@example')
353 self.write_line(b'MAIL from:spam@example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000354 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000355 b'503 Error: nested MAIL command\r\n')
356
R David Murrayd1a30c92012-05-26 14:33:59 -0400357 def test_VRFY(self):
358 self.write_line(b'VRFY eggs@example')
359 self.assertEqual(self.channel.socket.last,
360 b'252 Cannot VRFY user, but will accept message and attempt ' + \
361 b'delivery\r\n')
362
363 def test_VRFY_syntax(self):
364 self.write_line(b'VRFY')
365 self.assertEqual(self.channel.socket.last,
366 b'501 Syntax: VRFY <address>\r\n')
367
368 def test_EXPN_not_implemented(self):
369 self.write_line(b'EXPN')
370 self.assertEqual(self.channel.socket.last,
371 b'502 EXPN not implemented\r\n')
372
R David Murray669b7552012-03-20 16:16:29 -0400373 def test_no_HELO_MAIL(self):
374 self.write_line(b'MAIL from:<foo@example.com>')
375 self.assertEqual(self.channel.socket.last,
376 b'503 Error: send HELO first\r\n')
377
Richard Jones8cb36192010-07-23 16:20:40 +0000378 def test_need_RCPT(self):
R David Murray669b7552012-03-20 16:16:29 -0400379 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000380 self.write_line(b'MAIL From:eggs@example')
381 self.write_line(b'DATA')
Georg Brandl17e3d692010-07-31 10:08:09 +0000382 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000383 b'503 Error: need RCPT command\r\n')
384
R David Murrayd1a30c92012-05-26 14:33:59 -0400385 def test_RCPT_syntax_HELO(self):
R David Murray669b7552012-03-20 16:16:29 -0400386 self.write_line(b'HELO example')
R David Murrayd1a30c92012-05-26 14:33:59 -0400387 self.write_line(b'MAIL From: eggs@example')
Richard Jones8cb36192010-07-23 16:20:40 +0000388 self.write_line(b'RCPT to eggs@example')
Georg Brandl17e3d692010-07-31 10:08:09 +0000389 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000390 b'501 Syntax: RCPT TO: <address>\r\n')
391
R David Murrayd1a30c92012-05-26 14:33:59 -0400392 def test_RCPT_syntax_EHLO(self):
393 self.write_line(b'EHLO example')
394 self.write_line(b'MAIL From: eggs@example')
395 self.write_line(b'RCPT to eggs@example')
396 self.assertEqual(self.channel.socket.last,
397 b'501 Syntax: RCPT TO: <address> [SP <mail-parameters>]\r\n')
398
399 def test_RCPT_lowercase_to_OK(self):
400 self.write_line(b'HELO example')
401 self.write_line(b'MAIL From: eggs@example')
402 self.write_line(b'RCPT to: <eggs@example>')
403 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
404
R David Murray669b7552012-03-20 16:16:29 -0400405 def test_no_HELO_RCPT(self):
406 self.write_line(b'RCPT to eggs@example')
407 self.assertEqual(self.channel.socket.last,
408 b'503 Error: send HELO first\r\n')
409
Richard Jones8cb36192010-07-23 16:20:40 +0000410 def test_data_dialog(self):
R David Murray669b7552012-03-20 16:16:29 -0400411 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000412 self.write_line(b'MAIL From:eggs@example')
R David Murrayd1a30c92012-05-26 14:33:59 -0400413 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000414 self.write_line(b'RCPT To:spam@example')
R David Murrayd1a30c92012-05-26 14:33:59 -0400415 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000416
417 self.write_line(b'DATA')
Georg Brandl17e3d692010-07-31 10:08:09 +0000418 self.assertEqual(self.channel.socket.last,
Richard Jones8cb36192010-07-23 16:20:40 +0000419 b'354 End data with <CR><LF>.<CR><LF>\r\n')
420 self.write_line(b'data\r\nmore\r\n.')
R David Murrayd1a30c92012-05-26 14:33:59 -0400421 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000422 self.assertEqual(self.server.messages,
423 [('peer', 'eggs@example', ['spam@example'], 'data\nmore')])
Richard Jones8cb36192010-07-23 16:20:40 +0000424
425 def test_DATA_syntax(self):
R David Murray669b7552012-03-20 16:16:29 -0400426 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000427 self.write_line(b'MAIL From:eggs@example')
428 self.write_line(b'RCPT To:spam@example')
429 self.write_line(b'DATA spam')
Georg Brandl17e3d692010-07-31 10:08:09 +0000430 self.assertEqual(self.channel.socket.last, b'501 Syntax: DATA\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000431
R David Murray669b7552012-03-20 16:16:29 -0400432 def test_no_HELO_DATA(self):
433 self.write_line(b'DATA spam')
434 self.assertEqual(self.channel.socket.last,
435 b'503 Error: send HELO first\r\n')
436
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000437 def test_data_transparency_section_4_5_2(self):
R David Murray669b7552012-03-20 16:16:29 -0400438 self.write_line(b'HELO example')
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000439 self.write_line(b'MAIL From:eggs@example')
440 self.write_line(b'RCPT To:spam@example')
441 self.write_line(b'DATA')
442 self.write_line(b'..\r\n.\r\n')
443 self.assertEqual(self.channel.received_data, '.')
444
Richard Jones8cb36192010-07-23 16:20:40 +0000445 def test_multiple_RCPT(self):
R David Murray669b7552012-03-20 16:16:29 -0400446 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000447 self.write_line(b'MAIL From:eggs@example')
448 self.write_line(b'RCPT To:spam@example')
449 self.write_line(b'RCPT To:ham@example')
450 self.write_line(b'DATA')
451 self.write_line(b'data\r\n.')
Richard Jones6a9e6bb2010-08-04 12:27:36 +0000452 self.assertEqual(self.server.messages,
453 [('peer', 'eggs@example', ['spam@example','ham@example'], 'data')])
Richard Jones8cb36192010-07-23 16:20:40 +0000454
455 def test_manual_status(self):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000456 # checks that the Channel is able to return a custom status message
R David Murray669b7552012-03-20 16:16:29 -0400457 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000458 self.write_line(b'MAIL From:eggs@example')
459 self.write_line(b'RCPT To:spam@example')
460 self.write_line(b'DATA')
461 self.write_line(b'return status\r\n.')
Georg Brandl17e3d692010-07-31 10:08:09 +0000462 self.assertEqual(self.channel.socket.last, b'250 Okish\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000463
464 def test_RSET(self):
R David Murray669b7552012-03-20 16:16:29 -0400465 self.write_line(b'HELO example')
Richard Jones8cb36192010-07-23 16:20:40 +0000466 self.write_line(b'MAIL From:eggs@example')
467 self.write_line(b'RCPT To:spam@example')
468 self.write_line(b'RSET')
R David Murrayd1a30c92012-05-26 14:33:59 -0400469 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000470 self.write_line(b'MAIL From:foo@example')
471 self.write_line(b'RCPT To:eggs@example')
472 self.write_line(b'DATA')
473 self.write_line(b'data\r\n.')
Richard Jones6a9e6bb2010-08-04 12:27:36 +0000474 self.assertEqual(self.server.messages,
475 [('peer', 'foo@example', ['eggs@example'], 'data')])
Richard Jones8cb36192010-07-23 16:20:40 +0000476
R David Murray669b7552012-03-20 16:16:29 -0400477 def test_HELO_RSET(self):
478 self.write_line(b'HELO example')
479 self.write_line(b'RSET')
R David Murrayd1a30c92012-05-26 14:33:59 -0400480 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
R David Murray669b7552012-03-20 16:16:29 -0400481
Richard Jones8cb36192010-07-23 16:20:40 +0000482 def test_RSET_syntax(self):
483 self.write_line(b'RSET hi')
Georg Brandl17e3d692010-07-31 10:08:09 +0000484 self.assertEqual(self.channel.socket.last, b'501 Syntax: RSET\r\n')
Richard Jones8cb36192010-07-23 16:20:40 +0000485
R David Murrayd1a30c92012-05-26 14:33:59 -0400486 def test_unknown_command(self):
487 self.write_line(b'UNKNOWN_CMD')
488 self.assertEqual(self.channel.socket.last,
489 b'500 Error: command "UNKNOWN_CMD" not ' + \
490 b'recognized\r\n')
491
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000492 def test_attribute_deprecations(self):
Florent Xicluna67317752011-12-10 11:07:42 +0100493 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000494 spam = self.channel._SMTPChannel__server
Florent Xicluna67317752011-12-10 11:07:42 +0100495 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000496 self.channel._SMTPChannel__server = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100497 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000498 spam = self.channel._SMTPChannel__line
Florent Xicluna67317752011-12-10 11:07:42 +0100499 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000500 self.channel._SMTPChannel__line = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100501 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000502 spam = self.channel._SMTPChannel__state
Florent Xicluna67317752011-12-10 11:07:42 +0100503 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000504 self.channel._SMTPChannel__state = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100505 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000506 spam = self.channel._SMTPChannel__greeting
Florent Xicluna67317752011-12-10 11:07:42 +0100507 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000508 self.channel._SMTPChannel__greeting = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100509 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000510 spam = self.channel._SMTPChannel__mailfrom
Florent Xicluna67317752011-12-10 11:07:42 +0100511 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000512 self.channel._SMTPChannel__mailfrom = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100513 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000514 spam = self.channel._SMTPChannel__rcpttos
Florent Xicluna67317752011-12-10 11:07:42 +0100515 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000516 self.channel._SMTPChannel__rcpttos = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100517 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000518 spam = self.channel._SMTPChannel__data
Florent Xicluna67317752011-12-10 11:07:42 +0100519 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000520 self.channel._SMTPChannel__data = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100521 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000522 spam = self.channel._SMTPChannel__fqdn
Florent Xicluna67317752011-12-10 11:07:42 +0100523 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000524 self.channel._SMTPChannel__fqdn = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100525 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000526 spam = self.channel._SMTPChannel__peer
Florent Xicluna67317752011-12-10 11:07:42 +0100527 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000528 self.channel._SMTPChannel__peer = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100529 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000530 spam = self.channel._SMTPChannel__conn
Florent Xicluna67317752011-12-10 11:07:42 +0100531 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000532 self.channel._SMTPChannel__conn = 'spam'
Florent Xicluna67317752011-12-10 11:07:42 +0100533 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000534 spam = self.channel._SMTPChannel__addr
Florent Xicluna67317752011-12-10 11:07:42 +0100535 with support.check_warnings(('', DeprecationWarning)):
Richard Jones4aa0d4d2010-08-04 01:20:14 +0000536 self.channel._SMTPChannel__addr = 'spam'
Richard Jones8cb36192010-07-23 16:20:40 +0000537
R David Murray554bcbf2014-06-11 11:18:08 -0400538 def test_decode_data_default_warning(self):
R David Murray6fe56a32014-06-11 13:48:58 -0400539 server = DummyServer((support.HOST, 0), ('b', 0))
R David Murray554bcbf2014-06-11 11:18:08 -0400540 conn, addr = self.server.accept()
541 with self.assertWarns(DeprecationWarning):
542 smtpd.SMTPChannel(server, conn, addr)
543
R David Murray6fe56a32014-06-11 13:48:58 -0400544@unittest.skipUnless(support.IPV6_ENABLED, "IPv6 not enabled")
545class SMTPDChannelIPv6Test(SMTPDChannelTest):
546 def setUp(self):
547 smtpd.socket = asyncore.socket = mock_socket
548 self.old_debugstream = smtpd.DEBUGSTREAM
549 self.debug = smtpd.DEBUGSTREAM = io.StringIO()
550 self.server = DummyServer((support.HOSTv6, 0), ('b', 0))
551 conn, addr = self.server.accept()
552 self.channel = smtpd.SMTPChannel(self.server, conn, addr,
553 decode_data=True)
R David Murrayd1a30c92012-05-26 14:33:59 -0400554
555class SMTPDChannelWithDataSizeLimitTest(unittest.TestCase):
556
557 def setUp(self):
558 smtpd.socket = asyncore.socket = mock_socket
R David Murray05cab752012-06-04 15:55:51 -0400559 self.old_debugstream = smtpd.DEBUGSTREAM
R David Murrayd1a30c92012-05-26 14:33:59 -0400560 self.debug = smtpd.DEBUGSTREAM = io.StringIO()
R David Murray6fe56a32014-06-11 13:48:58 -0400561 self.server = DummyServer((support.HOST, 0), ('b', 0))
R David Murrayd1a30c92012-05-26 14:33:59 -0400562 conn, addr = self.server.accept()
563 # Set DATA size limit to 32 bytes for easy testing
R David Murray554bcbf2014-06-11 11:18:08 -0400564 self.channel = smtpd.SMTPChannel(self.server, conn, addr, 32,
565 decode_data=True)
R David Murrayd1a30c92012-05-26 14:33:59 -0400566
567 def tearDown(self):
568 asyncore.close_all()
569 asyncore.socket = smtpd.socket = socket
R David Murray05cab752012-06-04 15:55:51 -0400570 smtpd.DEBUGSTREAM = self.old_debugstream
R David Murrayd1a30c92012-05-26 14:33:59 -0400571
572 def write_line(self, line):
573 self.channel.socket.queue_recv(line)
574 self.channel.handle_read()
575
576 def test_data_limit_dialog(self):
577 self.write_line(b'HELO example')
578 self.write_line(b'MAIL From:eggs@example')
579 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
580 self.write_line(b'RCPT To:spam@example')
581 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
582
583 self.write_line(b'DATA')
584 self.assertEqual(self.channel.socket.last,
585 b'354 End data with <CR><LF>.<CR><LF>\r\n')
586 self.write_line(b'data\r\nmore\r\n.')
587 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
588 self.assertEqual(self.server.messages,
589 [('peer', 'eggs@example', ['spam@example'], 'data\nmore')])
590
591 def test_data_limit_dialog_too_much_data(self):
592 self.write_line(b'HELO example')
593 self.write_line(b'MAIL From:eggs@example')
594 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
595 self.write_line(b'RCPT To:spam@example')
596 self.assertEqual(self.channel.socket.last, b'250 OK\r\n')
597
598 self.write_line(b'DATA')
599 self.assertEqual(self.channel.socket.last,
600 b'354 End data with <CR><LF>.<CR><LF>\r\n')
601 self.write_line(b'This message is longer than 32 bytes\r\n.')
602 self.assertEqual(self.channel.socket.last,
603 b'552 Error: Too much mail data\r\n')
604
Richard Jones8cb36192010-07-23 16:20:40 +0000605
R David Murray554bcbf2014-06-11 11:18:08 -0400606class SMTPDChannelWithDecodeDataFalse(unittest.TestCase):
607
608 def setUp(self):
609 smtpd.socket = asyncore.socket = mock_socket
610 self.old_debugstream = smtpd.DEBUGSTREAM
611 self.debug = smtpd.DEBUGSTREAM = io.StringIO()
R David Murray6fe56a32014-06-11 13:48:58 -0400612 self.server = DummyServer((support.HOST, 0), ('b', 0),
613 decode_data=False)
R David Murray554bcbf2014-06-11 11:18:08 -0400614 conn, addr = self.server.accept()
615 # Set decode_data to False
616 self.channel = smtpd.SMTPChannel(self.server, conn, addr,
617 decode_data=False)
618
619 def tearDown(self):
620 asyncore.close_all()
621 asyncore.socket = smtpd.socket = socket
622 smtpd.DEBUGSTREAM = self.old_debugstream
623
624 def write_line(self, line):
625 self.channel.socket.queue_recv(line)
626 self.channel.handle_read()
627
628 def test_ascii_data(self):
629 self.write_line(b'HELO example')
630 self.write_line(b'MAIL From:eggs@example')
631 self.write_line(b'RCPT To:spam@example')
632 self.write_line(b'DATA')
633 self.write_line(b'plain ascii text')
634 self.write_line(b'.')
635 self.assertEqual(self.channel.received_data, b'plain ascii text')
636
637 def test_utf8_data(self):
638 self.write_line(b'HELO example')
639 self.write_line(b'MAIL From:eggs@example')
640 self.write_line(b'RCPT To:spam@example')
641 self.write_line(b'DATA')
642 self.write_line(b'utf8 enriched text: \xc5\xbc\xc5\xba\xc4\x87')
643 self.write_line(b'and some plain ascii')
644 self.write_line(b'.')
645 self.assertEqual(
646 self.channel.received_data,
647 b'utf8 enriched text: \xc5\xbc\xc5\xba\xc4\x87\n'
648 b'and some plain ascii')
649
650
651class SMTPDChannelWithDecodeDataTrue(unittest.TestCase):
652
653 def setUp(self):
654 smtpd.socket = asyncore.socket = mock_socket
655 self.old_debugstream = smtpd.DEBUGSTREAM
656 self.debug = smtpd.DEBUGSTREAM = io.StringIO()
R David Murray6fe56a32014-06-11 13:48:58 -0400657 self.server = DummyServer((support.HOST, 0), ('b', 0),
658 decode_data=True)
R David Murray554bcbf2014-06-11 11:18:08 -0400659 conn, addr = self.server.accept()
660 # Set decode_data to True
661 self.channel = smtpd.SMTPChannel(self.server, conn, addr,
662 decode_data=True)
663
664 def tearDown(self):
665 asyncore.close_all()
666 asyncore.socket = smtpd.socket = socket
667 smtpd.DEBUGSTREAM = self.old_debugstream
668
669 def write_line(self, line):
670 self.channel.socket.queue_recv(line)
671 self.channel.handle_read()
672
673 def test_ascii_data(self):
674 self.write_line(b'HELO example')
675 self.write_line(b'MAIL From:eggs@example')
676 self.write_line(b'RCPT To:spam@example')
677 self.write_line(b'DATA')
678 self.write_line(b'plain ascii text')
679 self.write_line(b'.')
680 self.assertEqual(self.channel.received_data, 'plain ascii text')
681
682 def test_utf8_data(self):
683 self.write_line(b'HELO example')
684 self.write_line(b'MAIL From:eggs@example')
685 self.write_line(b'RCPT To:spam@example')
686 self.write_line(b'DATA')
687 self.write_line(b'utf8 enriched text: \xc5\xbc\xc5\xba\xc4\x87')
688 self.write_line(b'and some plain ascii')
689 self.write_line(b'.')
690 self.assertEqual(
691 self.channel.received_data,
692 'utf8 enriched text: żźć\nand some plain ascii')
693
694
Richard Jones8cb36192010-07-23 16:20:40 +0000695if __name__ == "__main__":
R David Murrayd1a30c92012-05-26 14:33:59 -0400696 unittest.main()