|
1 | 1 | import asyncore |
2 | 2 | import email.mime.text |
3 | 3 | from email.message import EmailMessage |
| 4 | +from email.base64mime import body_encode as encode_base64 |
4 | 5 | import email.utils |
5 | 6 | import socket |
6 | 7 | import smtpd |
@@ -814,11 +815,11 @@ def testEHLO(self): |
814 | 815 | def testVRFY(self): |
815 | 816 | smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) |
816 | 817 |
|
817 | | - for email, name in sim_users.items(): |
| 818 | + for addr_spec, name in sim_users.items(): |
818 | 819 | expected_known = (250, bytes('%s %s' % |
819 | | - (name, smtplib.quoteaddr(email)), |
| 820 | + (name, smtplib.quoteaddr(addr_spec)), |
820 | 821 | "ascii")) |
821 | | - self.assertEqual(smtp.vrfy(email), expected_known) |
| 822 | + self.assertEqual(smtp.vrfy(addr_spec), expected_known) |
822 | 823 |
|
823 | 824 | |
824 | 825 | expected_unknown = (550, ('No such user: %s' % u).encode('ascii')) |
@@ -851,7 +852,7 @@ def testEXPN(self): |
851 | 852 | def testAUTH_PLAIN(self): |
852 | 853 | self.serv.add_feature("AUTH PLAIN") |
853 | 854 | smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', timeout=15) |
854 | | - try: smtp.login(sim_auth[0], sim_auth[1]) |
| 855 | + try: smtp.login(sim_auth[0], sim_auth[1], initial_response_ok=False) |
855 | 856 | except smtplib.SMTPAuthenticationError as err: |
856 | 857 | self.assertIn(sim_auth_plain, str(err)) |
857 | 858 | smtp.close() |
@@ -892,7 +893,7 @@ def test_auth_function(self): |
892 | 893 | 'LOGIN': smtp.auth_login, |
893 | 894 | } |
894 | 895 | for mechanism, method in supported.items(): |
895 | | - try: smtp.auth(mechanism, method) |
| 896 | + try: smtp.auth(mechanism, method, initial_response_ok=False) |
896 | 897 | except smtplib.SMTPAuthenticationError as err: |
897 | 898 | self.assertIn(sim_auth_credentials[mechanism.lower()].upper(), |
898 | 899 | str(err)) |
@@ -1142,12 +1143,85 @@ def test_send_message_error_on_non_ascii_addrs_if_no_smtputf8(self): |
1142 | 1143 | smtp.send_message(msg)) |
1143 | 1144 |
|
1144 | 1145 |
|
| 1146 | +EXPECTED_RESPONSE = encode_base64(b'\0psu\0doesnotexist', eol='') |
| 1147 | + |
| 1148 | +class SimSMTPAUTHInitialResponseChannel(SimSMTPChannel): |
| 1149 | + def smtp_AUTH(self, arg): |
| 1150 | + # RFC 4954's AUTH command allows for an optional initial-response. |
| 1151 | + # Not all AUTH methods support this; some require a challenge. AUTH |
| 1152 | + # PLAIN does those, so test that here. See issue #15014. |
| 1153 | + args = arg.split() |
| 1154 | + if args[0].lower() == 'plain': |
| 1155 | + if len(args) == 2: |
| 1156 | + # AUTH PLAIN <initial-response> with the response base 64 |
| 1157 | + # encoded. Hard code the expected response for the test. |
| 1158 | + if args[1] == EXPECTED_RESPONSE: |
| 1159 | + self.push('235 Ok') |
| 1160 | + return |
| 1161 | + self.push('571 Bad authentication') |
| 1162 | + |
| 1163 | +class SimSMTPAUTHInitialResponseServer(SimSMTPServer): |
| 1164 | + channel_class = SimSMTPAUTHInitialResponseChannel |
| 1165 | + |
| 1166 | + |
| 1167 | +@unittest.skipUnless(threading, 'Threading required for this test.') |
| 1168 | +class SMTPAUTHInitialResponseSimTests(unittest.TestCase): |
| 1169 | + def setUp(self): |
| 1170 | + self.real_getfqdn = socket.getfqdn |
| 1171 | + socket.getfqdn = mock_socket.getfqdn |
| 1172 | + self.serv_evt = threading.Event() |
| 1173 | + self.client_evt = threading.Event() |
| 1174 | + # Pick a random unused port by passing 0 for the port number |
| 1175 | + self.serv = SimSMTPAUTHInitialResponseServer( |
| 1176 | + (HOST, 0), ('nowhere', -1), decode_data=True) |
| 1177 | + # Keep a note of what port was assigned |
| 1178 | + self.port = self.serv.socket.getsockname()[1] |
| 1179 | + serv_args = (self.serv, self.serv_evt, self.client_evt) |
| 1180 | + self.thread = threading.Thread(target=debugging_server, args=serv_args) |
| 1181 | + self.thread.start() |
| 1182 | + |
| 1183 | + # wait until server thread has assigned a port number |
| 1184 | + self.serv_evt.wait() |
| 1185 | + self.serv_evt.clear() |
| 1186 | + |
| 1187 | + def tearDown(self): |
| 1188 | + socket.getfqdn = self.real_getfqdn |
| 1189 | + # indicate that the client is finished |
| 1190 | + self.client_evt.set() |
| 1191 | + # wait for the server thread to terminate |
| 1192 | + self.serv_evt.wait() |
| 1193 | + self.thread.join() |
| 1194 | + |
| 1195 | + def testAUTH_PLAIN_initial_response_login(self): |
| 1196 | + self.serv.add_feature('AUTH PLAIN') |
| 1197 | + smtp = smtplib.SMTP(HOST, self.port, |
| 1198 | + local_hostname='localhost', timeout=15) |
| 1199 | + smtp.login('psu', 'doesnotexist') |
| 1200 | + smtp.close() |
| 1201 | + |
| 1202 | + def testAUTH_PLAIN_initial_response_auth(self): |
| 1203 | + self.serv.add_feature('AUTH PLAIN') |
| 1204 | + smtp = smtplib.SMTP(HOST, self.port, |
| 1205 | + local_hostname='localhost', timeout=15) |
| 1206 | + smtp.user = 'psu' |
| 1207 | + smtp.password = 'doesnotexist' |
| 1208 | + code, response = smtp.auth('plain', smtp.auth_plain) |
| 1209 | + smtp.close() |
| 1210 | + self.assertEqual(code, 235) |
| 1211 | + |
| 1212 | + |
1145 | 1213 | @support.reap_threads |
1146 | 1214 | def test_main(verbose=None): |
1147 | | - support.run_unittest(GeneralTests, DebuggingServerTests, |
1148 | | - NonConnectingTests, |
1149 | | - BadHELOServerTests, SMTPSimTests, |
1150 | | - TooLongLineTests) |
| 1215 | + support.run_unittest( |
| 1216 | + BadHELOServerTests, |
| 1217 | + DebuggingServerTests, |
| 1218 | + GeneralTests, |
| 1219 | + NonConnectingTests, |
| 1220 | + SMTPAUTHInitialResponseSimTests, |
| 1221 | + SMTPSimTests, |
| 1222 | + TooLongLineTests, |
| 1223 | + ) |
| 1224 | + |
1151 | 1225 |
|
1152 | 1226 | if __name__ == '__main__': |
1153 | 1227 | test_main() |
0 commit comments