]> granicus.if.org Git - python/commitdiff
Merge: #5713: Handle 421 error codes during sendmail by closing the socket.
authorR David Murray <rdmurray@bitdance.com>
Thu, 21 Mar 2013 01:12:17 +0000 (21:12 -0400)
committerR David Murray <rdmurray@bitdance.com>
Thu, 21 Mar 2013 01:12:17 +0000 (21:12 -0400)
This is a partial fix to the issue of servers disconnecting unexpectedly; in
this case the 421 says they are disconnecting, so we close the socket and
return the 421 in the appropriate error context.

Original patch by Mark Sapiro, updated by Kushal Das, with additional
tests by me.

1  2 
Lib/smtplib.py
Lib/test/test_smtplib.py
Misc/NEWS

diff --cc Lib/smtplib.py
Simple merge
index befc49e9d3d72b724790fe0688611382908f9388,92f986b72ee0dc3b084eff2ccd2c3c3341964d5c..c0d9dbd0c47e8e4edb087e9a21c88689e1c6c0d9
@@@ -592,8 -560,11 +592,12 @@@ sim_lists = {'list-1':['Mr.A@somewhere.
  # Simulated SMTP channel & server
  class SimSMTPChannel(smtpd.SMTPChannel):
  
-     # For testing failures in QUIT when using the context manager API.
 +    quit_response = None
+     mail_response = None
+     rcpt_response = None
+     data_response = None
+     rcpt_count = 0
+     rset_count = 0
  
      def __init__(self, extra_features, *args, **kw):
          self._extrafeatures = ''.join(
                  '250-DELIVERBY\r\n')
          resp = resp + self._extrafeatures + '250 HELP'
          self.push(resp)
++        self.seen_greeting = arg
++        self.extended_smtp = True
  
      def smtp_VRFY(self, arg):
          # For max compatibility smtplib should be sending the raw address.
          else:
              self.push('550 No access for you!')
  
-         # args is ignored
 +    def smtp_QUIT(self, arg):
 +        if self.quit_response is None:
 +            super(SimSMTPChannel, self).smtp_QUIT(arg)
 +        else:
 +            self.push(self.quit_response)
 +            self.close_when_done()
 +
+     def smtp_MAIL(self, arg):
+         if self.mail_response is None:
+             super().smtp_MAIL(arg)
+         else:
+             self.push(self.mail_response)
+     def smtp_RCPT(self, arg):
+         if self.rcpt_response is None:
+             super().smtp_RCPT(arg)
+             return
+         self.push(self.rcpt_response[self.rcpt_count])
+         self.rcpt_count += 1
+     def smtp_RSET(self, arg):
+         super().smtp_RSET(arg)
+         self.rset_count += 1
+     def smtp_DATA(self, arg):
+         if self.data_response is None:
+             super().smtp_DATA(arg)
+         else:
+             self.push(self.data_response)
      def handle_error(self):
          raise
  
@@@ -667,9 -652,8 +694,8 @@@ class SimSMTPServer(smtpd.SMTPServer)
          smtpd.SMTPServer.__init__(self, *args, **kw)
  
      def handle_accepted(self, conn, addr):
-         self._SMTPchannel = SimSMTPChannel(
 -        self._SMTPchannel = self.channel_class(self._extra_features,
 -                                           self, conn, addr)
++        self._SMTPchannel = self.channel_class(
 +            self._extra_features, self, conn, addr)
-         self._SMTPchannel.quit_response = self.quit_response
  
      def process_message(self, peer, mailfrom, rcpttos, data):
          pass
@@@ -799,25 -783,6 +825,23 @@@ class SMTPSimTests(unittest.TestCase)
              self.assertIn(sim_auth_credentials['cram-md5'], str(err))
          smtp.close()
  
-         self.serv.quit_response = '421 QUIT FAILED'
 +    def test_with_statement(self):
 +        with smtplib.SMTP(HOST, self.port) as smtp:
 +            code, message = smtp.noop()
 +            self.assertEqual(code, 250)
 +        self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo')
 +        with smtplib.SMTP(HOST, self.port) as smtp:
 +            smtp.close()
 +        self.assertRaises(smtplib.SMTPServerDisconnected, smtp.send, b'foo')
 +
 +    def test_with_statement_QUIT_failure(self):
-         # We don't need to clean up self.serv.quit_response because a new
-         # server is always instantiated in the setUp().
 +        with self.assertRaises(smtplib.SMTPResponseException) as error:
 +            with smtplib.SMTP(HOST, self.port) as smtp:
++                self.serv._SMTPchannel.quit_response = '421 QUIT FAILED'
 +                smtp.noop()
 +        self.assertEqual(error.exception.smtp_code, 421)
 +        self.assertEqual(error.exception.smtp_error, b'QUIT FAILED')
 +
      #TODO: add tests for correct AUTH method fallback now that the
      #test infrastructure can support it.
  
diff --cc Misc/NEWS
index 1377e5964daf5402214d7807dfb120f121232e4b,20ffcc9bd6be6705d1db798a639ec8c875bb18d2..fe5afdd590038b72daefc9daa73a28764383c9c9
+++ b/Misc/NEWS
@@@ -196,13 -233,13 +196,17 @@@ Core and Builtin
  Library
  -------
  
+ - Issue #5713: smtplib now handles 421 (closing connection) error codes when
+   sending mail by closing the socket and reporting the 421 error code via the
+   exception appropriate to the command that received the error response.
 +- Issue #17192: Update the ctypes module's libffi to v3.0.13.  This
 +  specifically addresses a stack misalignment issue on x86 and issues on
 +  some more recent platforms.
 +
  - Issue #8862: Fixed curses cleanup when getkey is interrputed by a signal.
  
 -- Issue #17443: impalib.IMAP4_stream was using the default unbuffered IO
 +- Issue #17443: imaplib.IMAP4_stream was using the default unbuffered IO
    in subprocess, but the imap code assumes buffered IO.  In Python2 this
    worked by accident.  IMAP4_stream now explicitly uses buffered IO.