| 
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