|
17 | 17 | import threading
|
18 | 18 |
|
19 | 19 | import unittest
|
| 20 | +import unittest.mock as mock |
20 | 21 | from test import support, mock_socket
|
21 | 22 | from test.support import hashlib_helper
|
22 | 23 | from test.support import socket_helper
|
@@ -926,11 +927,14 @@ def _auth_cram_md5(self, arg=None):
|
926 | 927 | except ValueError as e:
|
927 | 928 | self.push('535 Splitting response {!r} into user and password '
|
928 | 929 | 'failed: {}'.format(logpass, e))
|
929 |
| - return False |
930 |
| - valid_hashed_pass = hmac.HMAC( |
931 |
| - sim_auth[1].encode('ascii'), |
932 |
| - self._decode_base64(sim_cram_md5_challenge).encode('ascii'), |
933 |
| - 'md5').hexdigest() |
| 930 | + return |
| 931 | + pwd = sim_auth[1].encode('ascii') |
| 932 | + msg = self._decode_base64(sim_cram_md5_challenge).encode('ascii') |
| 933 | + try: |
| 934 | + valid_hashed_pass = hmac.HMAC(pwd, msg, 'md5').hexdigest() |
| 935 | + except ValueError: |
| 936 | + self.push('504 CRAM-MD5 is not supported') |
| 937 | + return |
934 | 938 | self._authenticated(user, hashed_pass == valid_hashed_pass)
|
935 | 939 | # end AUTH related stuff.
|
936 | 940 |
|
@@ -1181,6 +1185,39 @@ def testAUTH_CRAM_MD5(self):
|
1181 | 1185 | self.assertEqual(resp, (235, b'Authentication Succeeded'))
|
1182 | 1186 | smtp.close()
|
1183 | 1187 |
|
| 1188 | + @mock.patch("hmac.HMAC") |
| 1189 | + @mock.patch("smtplib._have_cram_md5_support", False) |
| 1190 | + def testAUTH_CRAM_MD5_blocked(self, hmac_constructor): |
| 1191 | + # CRAM-MD5 is the only "known" method by the server, |
| 1192 | + # but it is not supported by the client. In particular, |
| 1193 | + # no challenge will ever be sent. |
| 1194 | + self.serv.add_feature("AUTH CRAM-MD5") |
| 1195 | + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', |
| 1196 | + timeout=support.LOOPBACK_TIMEOUT) |
| 1197 | + self.addCleanup(smtp.close) |
| 1198 | + msg = re.escape("No suitable authentication method found.") |
| 1199 | + with self.assertRaisesRegex(smtplib.SMTPException, msg): |
| 1200 | + smtp.login(sim_auth[0], sim_auth[1]) |
| 1201 | + hmac_constructor.assert_not_called() # call has been bypassed |
| 1202 | + |
| 1203 | + @mock.patch("smtplib._have_cram_md5_support", False) |
| 1204 | + def testAUTH_CRAM_MD5_blocked_and_fallback(self): |
| 1205 | + # Test that PLAIN is tried after CRAM-MD5 failed |
| 1206 | + self.serv.add_feature("AUTH CRAM-MD5 PLAIN") |
| 1207 | + smtp = smtplib.SMTP(HOST, self.port, local_hostname='localhost', |
| 1208 | + timeout=support.LOOPBACK_TIMEOUT) |
| 1209 | + self.addCleanup(smtp.close) |
| 1210 | + with ( |
| 1211 | + mock.patch.object(smtp, "auth_cram_md5") as smtp_auth_cram_md5, |
| 1212 | + mock.patch.object( |
| 1213 | + smtp, "auth_plain", wraps=smtp.auth_plain |
| 1214 | + ) as smtp_auth_plain |
| 1215 | + ): |
| 1216 | + resp = smtp.login(sim_auth[0], sim_auth[1]) |
| 1217 | + smtp_auth_plain.assert_called_once() |
| 1218 | + smtp_auth_cram_md5.assert_not_called() # no call to HMAC constructor |
| 1219 | + self.assertEqual(resp, (235, b'Authentication Succeeded')) |
| 1220 | + |
1184 | 1221 | @hashlib_helper.requires_hashdigest('md5', openssl=True)
|
1185 | 1222 | def testAUTH_multiple(self):
|
1186 | 1223 | # Test that multiple authentication methods are tried.
|
|
0 commit comments