1717import threading
1818
1919import unittest
20+ import unittest .mock as mock
2021from test import support , mock_socket
2122from test .support import hashlib_helper
2223from test .support import socket_helper
@@ -350,7 +351,7 @@ def testVRFY(self):
350351 timeout = support .LOOPBACK_TIMEOUT )
351352 self .addCleanup (smtp .close )
352353 expected = (252 , b'Cannot VRFY user, but will accept message ' + \
353- b'and attempt delivery' )
354+ b'and attempt delivery' )
354355 self .
assertEqual (
smtp .
vrfy (
'[email protected] ' ),
expected )
355356 self .
assertEqual (
smtp .
verify (
'[email protected] ' ),
expected )
356357 smtp .quit ()
@@ -371,7 +372,7 @@ def testHELP(self):
371372 timeout = support .LOOPBACK_TIMEOUT )
372373 self .addCleanup (smtp .close )
373374 self .assertEqual (smtp .help (), b'Supported commands: EHLO HELO MAIL ' + \
374- b'RCPT DATA RSET NOOP QUIT VRFY' )
375+ b'RCPT DATA RSET NOOP QUIT VRFY' )
375376 smtp .quit ()
376377
377378 def testSend (self ):
@@ -527,7 +528,7 @@ def testSendMessageWithAddresses(self):
527528 smtp .quit ()
528529 # make sure the Bcc header is still in the message.
529530 self .assertEqual (m ['Bcc' ], 'John Root <root@localhost>, "Dinsdale" '
530- 531+ 531532
532533 self .client_evt .set ()
533534 self .serv_evt .wait ()
@@ -766,7 +767,7 @@ def tearDown(self):
766767
767768 def testFailingHELO (self ):
768769 self .assertRaises (smtplib .SMTPConnectError , smtplib .SMTP ,
769- HOST , self .port , 'localhost' , 3 )
770+ HOST , self .port , 'localhost' , 3 )
770771
771772
772773class TooLongLineTests (unittest .TestCase ):
@@ -804,14 +805,14 @@ def testLineTooLong(self):
804805sim_users = {
'[email protected] ' :
'John A' ,
805806806807807- }
808+ }
808809
809810sim_auth = (
'[email protected] ' ,
'somepassword' )
810811sim_cram_md5_challenge = ('PENCeUxFREJoU0NnbmhNWitOMjNGNn'
811812 'dAZWx3b29kLmlubm9zb2Z0LmNvbT4=' )
812813813814814- }
815+ }
815816
816817# Simulated SMTP channel & server
817818class ResponseException (Exception ): pass
@@ -830,6 +831,7 @@ class SimSMTPChannel(smtpd.SMTPChannel):
830831 def __init__ (self , extra_features , * args , ** kw ):
831832 self ._extrafeatures = '' .join (
832833 [ "250-{0}\r \n " .format (x ) for x in extra_features ])
834+ self .all_received_lines = []
833835 super (SimSMTPChannel , self ).__init__ (* args , ** kw )
834836
835837 # AUTH related stuff. It would be nice if support for this were in smtpd.
@@ -844,6 +846,7 @@ def found_terminator(self):
844846 self .smtp_state = self .COMMAND
845847 self .push ('%s %s' % (e .smtp_code , e .smtp_error ))
846848 return
849+ self .all_received_lines .append (self .received_lines )
847850 super ().found_terminator ()
848851
849852
@@ -924,11 +927,14 @@ def _auth_cram_md5(self, arg=None):
924927 except ValueError as e :
925928 self .push ('535 Splitting response {!r} into user and password '
926929 'failed: {}' .format (logpass , e ))
927- return False
928- valid_hashed_pass = hmac .HMAC (
929- sim_auth [1 ].encode ('ascii' ),
930- self ._decode_base64 (sim_cram_md5_challenge ).encode ('ascii' ),
931- '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
932938 self ._authenticated (user , hashed_pass == valid_hashed_pass )
933939 # end AUTH related stuff.
934940
@@ -1170,8 +1176,7 @@ def auth_buggy(challenge=None):
11701176 finally :
11711177 smtp .close ()
11721178
1173- # TODO: RUSTPYTHON
1174- @unittest .expectedFailure
1179+ @unittest .expectedFailure # TODO: RUSTPYTHON
11751180 @hashlib_helper .requires_hashdigest ('md5' , openssl = True )
11761181 def testAUTH_CRAM_MD5 (self ):
11771182 self .serv .add_feature ("AUTH CRAM-MD5" )
@@ -1181,8 +1186,40 @@ def testAUTH_CRAM_MD5(self):
11811186 self .assertEqual (resp , (235 , b'Authentication Succeeded' ))
11821187 smtp .close ()
11831188
1184- # TODO: RUSTPYTHON
1185- @unittest .expectedFailure
1189+ @mock .patch ("hmac.HMAC" )
1190+ @mock .patch ("smtplib._have_cram_md5_support" , False )
1191+ def testAUTH_CRAM_MD5_blocked (self , hmac_constructor ):
1192+ # CRAM-MD5 is the only "known" method by the server,
1193+ # but it is not supported by the client. In particular,
1194+ # no challenge will ever be sent.
1195+ self .serv .add_feature ("AUTH CRAM-MD5" )
1196+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' ,
1197+ timeout = support .LOOPBACK_TIMEOUT )
1198+ self .addCleanup (smtp .close )
1199+ msg = re .escape ("No suitable authentication method found." )
1200+ with self .assertRaisesRegex (smtplib .SMTPException , msg ):
1201+ smtp .login (sim_auth [0 ], sim_auth [1 ])
1202+ hmac_constructor .assert_not_called () # call has been bypassed
1203+
1204+ @mock .patch ("smtplib._have_cram_md5_support" , False )
1205+ def testAUTH_CRAM_MD5_blocked_and_fallback (self ):
1206+ # Test that PLAIN is tried after CRAM-MD5 failed
1207+ self .serv .add_feature ("AUTH CRAM-MD5 PLAIN" )
1208+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' ,
1209+ timeout = support .LOOPBACK_TIMEOUT )
1210+ self .addCleanup (smtp .close )
1211+ with (
1212+ mock .patch .object (smtp , "auth_cram_md5" ) as smtp_auth_cram_md5 ,
1213+ mock .patch .object (
1214+ smtp , "auth_plain" , wraps = smtp .auth_plain
1215+ ) as smtp_auth_plain
1216+ ):
1217+ resp = smtp .login (sim_auth [0 ], sim_auth [1 ])
1218+ smtp_auth_plain .assert_called_once ()
1219+ smtp_auth_cram_md5 .assert_not_called () # no call to HMAC constructor
1220+ self .assertEqual (resp , (235 , b'Authentication Succeeded' ))
1221+
1222+ @unittest .expectedFailure # TODO: RUSTPYTHON
11861223 @hashlib_helper .requires_hashdigest ('md5' , openssl = True )
11871224 def testAUTH_multiple (self ):
11881225 # Test that multiple authentication methods are tried.
@@ -1193,8 +1230,7 @@ def testAUTH_multiple(self):
11931230 self .assertEqual (resp , (235 , b'Authentication Succeeded' ))
11941231 smtp .close ()
11951232
1196- # TODO: RUSTPYTHON
1197- @unittest .expectedFailure
1233+ @unittest .expectedFailure # TODO: RUSTPYTHON
11981234 def test_auth_function (self ):
11991235 supported = {'PLAIN' , 'LOGIN' }
12001236 try :
@@ -1354,6 +1390,18 @@ def test_name_field_not_included_in_envelop_addresses(self):
13541390 self .
assertEqual (
self .
serv .
_addresses [
'from' ],
'[email protected] ' )
13551391 self .
assertEqual (
self .
serv .
_addresses [
'tos' ], [
'[email protected] ' ])
13561392
1393+ def test_lowercase_mail_from_rcpt_to (self ):
1394+ m = 'A test message'
1395+ smtp = smtplib .SMTP (
1396+ HOST , self .port , local_hostname = 'localhost' ,
1397+ timeout = support .LOOPBACK_TIMEOUT )
1398+ self .addCleanup (smtp .close )
1399+
1400+ smtp .sendmail ('John' , 'Sally' , m )
1401+
1402+ self .assertIn (['mail from:<John> size=14' ], self .serv ._SMTPchannel .all_received_lines )
1403+ self .assertIn (['rcpt to:<Sally>' ], self .serv ._SMTPchannel .all_received_lines )
1404+
13571405
13581406class SimSMTPUTF8Server (SimSMTPServer ):
13591407
@@ -1372,7 +1420,7 @@ def handle_accepted(self, conn, addr):
13721420 )
13731421
13741422 def process_message (self , peer , mailfrom , rcpttos , data , mail_options = None ,
1375- rcpt_options = None ):
1423+ rcpt_options = None ):
13761424 self .last_peer = peer
13771425 self .last_mailfrom = mailfrom
13781426 self .last_rcpttos = rcpttos
0 commit comments