@@ -586,8 +586,12 @@ def testFailingHELO(self):
586586# Simulated SMTP channel & server
587587class SimSMTPChannel (smtpd .SMTPChannel ):
588588
589- # For testing failures in QUIT when using the context manager API.
590589 quit_response = None
590+ mail_response = None
591+ rcpt_response = None
592+ data_response = None
593+ rcpt_count = 0
594+ rset_count = 0
591595
592596 def __init__ (self , extra_features , * args , ** kw ):
593597 self ._extrafeatures = '' .join (
@@ -602,6 +606,8 @@ def smtp_EHLO(self, arg):
602606 '250-DELIVERBY\r \n ' )
603607 resp = resp + self ._extrafeatures + '250 HELP'
604608 self .push (resp )
609+ self .seen_greeting = arg
610+ self .extended_smtp = True
605611
606612 def smtp_VRFY (self , arg ):
607613 # For max compatibility smtplib should be sending the raw address.
@@ -640,30 +646,50 @@ def smtp_AUTH(self, arg):
640646 self .push ('550 No access for you!' )
641647
642648 def smtp_QUIT (self , arg ):
643- # args is ignored
644649 if self .quit_response is None :
645650 super (SimSMTPChannel , self ).smtp_QUIT (arg )
646651 else :
647652 self .push (self .quit_response )
648653 self .close_when_done ()
649654
655+ def smtp_MAIL (self , arg ):
656+ if self .mail_response is None :
657+ super ().smtp_MAIL (arg )
658+ else :
659+ self .push (self .mail_response )
660+
661+ def smtp_RCPT (self , arg ):
662+ if self .rcpt_response is None :
663+ super ().smtp_RCPT (arg )
664+ return
665+ self .push (self .rcpt_response [self .rcpt_count ])
666+ self .rcpt_count += 1
667+
668+ def smtp_RSET (self , arg ):
669+ super ().smtp_RSET (arg )
670+ self .rset_count += 1
671+
672+ def smtp_DATA (self , arg ):
673+ if self .data_response is None :
674+ super ().smtp_DATA (arg )
675+ else :
676+ self .push (self .data_response )
677+
650678 def handle_error (self ):
651679 raise
652680
653681
654682class SimSMTPServer (smtpd .SMTPServer ):
655683
656- # For testing failures in QUIT when using the context manager API.
657- quit_response = None
684+ channel_class = SimSMTPChannel
658685
659686 def __init__ (self , * args , ** kw ):
660687 self ._extra_features = []
661688 smtpd .SMTPServer .__init__ (self , * args , ** kw )
662689
663690 def handle_accepted (self , conn , addr ):
664- self ._SMTPchannel = SimSMTPChannel (
691+ self ._SMTPchannel = self . channel_class (
665692 self ._extra_features , self , conn , addr )
666- self ._SMTPchannel .quit_response = self .quit_response
667693
668694 def process_message (self , peer , mailfrom , rcpttos , data ):
669695 pass
@@ -803,18 +829,48 @@ def test_with_statement(self):
803829 self .assertRaises (smtplib .SMTPServerDisconnected , smtp .send , b'foo' )
804830
805831 def test_with_statement_QUIT_failure (self ):
806- self .serv .quit_response = '421 QUIT FAILED'
807832 with self .assertRaises (smtplib .SMTPResponseException ) as error :
808833 with smtplib .SMTP (HOST , self .port ) as smtp :
834+ self .serv ._SMTPchannel .quit_response = '421 QUIT FAILED'
809835 smtp .noop ()
810836 self .assertEqual (error .exception .smtp_code , 421 )
811837 self .assertEqual (error .exception .smtp_error , b'QUIT FAILED' )
812- # We don't need to clean up self.serv.quit_response because a new
813- # server is always instantiated in the setUp().
814838
815839 #TODO: add tests for correct AUTH method fallback now that the
816840 #test infrastructure can support it.
817841
842+ # Issue 5713: make sure close, not rset, is called if we get a 421 error
843+ def test_421_from_mail_cmd (self ):
844+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
845+ self .serv ._SMTPchannel .mail_response = '421 closing connection'
846+ with self .assertRaises (smtplib .SMTPSenderRefused ):
847+ smtp .sendmail ('John' , 'Sally' , 'test message' )
848+ self .assertIsNone (smtp .sock )
849+ self .assertEqual (self .serv ._SMTPchannel .rcpt_count , 0 )
850+
851+ def test_421_from_rcpt_cmd (self ):
852+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
853+ self .serv ._SMTPchannel .rcpt_response = ['250 accepted' , '421 closing' ]
854+ with self .assertRaises (smtplib .SMTPRecipientsRefused ) as r :
855+ smtp .sendmail ('John' , ['Sally' , 'Frank' , 'George' ], 'test message' )
856+ self .assertIsNone (smtp .sock )
857+ self .assertEqual (self .serv ._SMTPchannel .rset_count , 0 )
858+ self .assertDictEqual (r .exception .args [0 ], {'Frank' : (421 , b'closing' )})
859+
860+ def test_421_from_data_cmd (self ):
861+ class MySimSMTPChannel (SimSMTPChannel ):
862+ def found_terminator (self ):
863+ if self .smtp_state == self .DATA :
864+ self .push ('421 closing' )
865+ else :
866+ super ().found_terminator ()
867+ self .serv .channel_class = MySimSMTPChannel
868+ smtp = smtplib .SMTP (HOST , self .port , local_hostname = 'localhost' , timeout = 15 )
869+ with self .assertRaises (smtplib .SMTPDataError ):
870+ smtp .
sendmail (
'[email protected] ' , [
'[email protected] ' ],
'test message' )
871+ self .assertIsNone (smtp .sock )
872+ self .assertEqual (self .serv ._SMTPchannel .rcpt_count , 0 )
873+
818874
819875@support .reap_threads
820876def test_main (verbose = None ):
0 commit comments