@@ -560,6 +560,12 @@ def testFailingHELO(self):
560560# Simulated SMTP channel & server
561561class SimSMTPChannel (smtpd .SMTPChannel ):
562562
563+ mail_response = None
564+ rcpt_response = None
565+ data_response = None
566+ rcpt_count = 0
567+ rset_count = 0
568+
563569 def __init__ (self , extra_features , * args , ** kw ):
564570 self ._extrafeatures = '' .join (
565571 [ "250-{0}\r \n " .format (x ) for x in extra_features ])
@@ -610,18 +616,43 @@ def smtp_AUTH(self, arg):
610616 else :
611617 self .push ('550 No access for you!' )
612618
619+ def smtp_MAIL (self , arg ):
620+ if self .mail_response is None :
621+ super ().smtp_MAIL (arg )
622+ else :
623+ self .push (self .mail_response )
624+
625+ def smtp_RCPT (self , arg ):
626+ if self .rcpt_response is None :
627+ super ().smtp_RCPT (arg )
628+ return
629+ self .push (self .rcpt_response [self .rcpt_count ])
630+ self .rcpt_count += 1
631+
632+ def smtp_RSET (self , arg ):
633+ super ().smtp_RSET (arg )
634+ self .rset_count += 1
635+
636+ def smtp_DATA (self , arg ):
637+ if self .data_response is None :
638+ super ().smtp_DATA (arg )
639+ else :
640+ self .push (self .data_response )
641+
613642 def handle_error (self ):
614643 raise
615644
616645
617646class SimSMTPServer (smtpd .SMTPServer ):
618647
648+ channel_class = SimSMTPChannel
649+
619650 def __init__ (self , * args , ** kw ):
620651 self ._extra_features = []
621652 smtpd .SMTPServer .__init__ (self , * args , ** kw )
622653
623654 def handle_accepted (self , conn , addr ):
624- self ._SMTPchannel = SimSMTPChannel (self ._extra_features ,
655+ self ._SMTPchannel = self . channel_class (self ._extra_features ,
625656 self , conn , addr )
626657
627658 def process_message (self , peer , mailfrom , rcpttos , data ):
@@ -755,6 +786,38 @@ def testAUTH_CRAM_MD5(self):
755786 #TODO: add tests for correct AUTH method fallback now that the
756787 #test infrastructure can support it.
757788
789+ # Issue 5713: make sure close, not rset, is called if we get a 421 error
790+ def test_421_from_mail_cmd (self ):
791+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
792+ self .serv ._SMTPchannel .mail_response = '421 closing connection'
793+ with self .assertRaises (smtplib .SMTPSenderRefused ):
794+ smtp .sendmail ('John' , 'Sally' , 'test message' )
795+ self .assertIsNone (smtp .sock )
796+ self .assertEqual (self .serv ._SMTPchannel .rcpt_count , 0 )
797+
798+ def test_421_from_rcpt_cmd (self ):
799+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
800+ self .serv ._SMTPchannel .rcpt_response = ['250 accepted' , '421 closing' ]
801+ with self .assertRaises (smtplib .SMTPRecipientsRefused ) as r :
802+ smtp .sendmail ('John' , ['Sally' , 'Frank' , 'George' ], 'test message' )
803+ self .assertIsNone (smtp .sock )
804+ self .assertEqual (self .serv ._SMTPchannel .rset_count , 0 )
805+ self .assertDictEqual (r .exception .args [0 ], {'Frank' : (421 , b'closing' )})
806+
807+ def test_421_from_data_cmd (self ):
808+ class MySimSMTPChannel (SimSMTPChannel ):
809+ def found_terminator (self ):
810+ if self .smtp_state == self .DATA :
811+ self .push ('421 closing' )
812+ else :
813+ super ().found_terminator ()
814+ self .serv .channel_class = MySimSMTPChannel
815+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
816+ with self .assertRaises (smtplib .SMTPDataError ):
817+ smtp .
sendmail (
'[email protected] ' , [
'[email protected] ' ],
'test message' )
818+ self .assertIsNone (smtp .sock )
819+ self .assertEqual (self .serv ._SMTPchannel .rcpt_count , 0 )
820+
758821
759822@support .reap_threads
760823def test_main (verbose = None ):
0 commit comments