Rev 2925: (James Henstridge) Update SMTPConnection to better follow the SMTP spec. in file:///home/pqm/archives/thelove/bzr/%2Btrunk/

Canonical.com Patch Queue Manager pqm at pqm.ubuntu.com
Mon Oct 22 19:35:49 BST 2007


At file:///home/pqm/archives/thelove/bzr/%2Btrunk/

------------------------------------------------------------
revno: 2925
revision-id: pqm at pqm.ubuntu.com-20071022183547-ylcflvri4vahgtmg
parent: pqm at pqm.ubuntu.com-20071022175339-ueibdaois8t36171
parent: james at jamesh.id.au-20071020003544-ol0k71n7vgefhlwd
committer: Canonical.com Patch Queue Manager <pqm at pqm.ubuntu.com>
branch nick: +trunk
timestamp: Mon 2007-10-22 19:35:47 +0100
message:
  (James Henstridge) Update SMTPConnection to better follow the SMTP spec.
modified:
  bzrlib/smtp_connection.py      smtp_connection.py-20070618204456-nu6wag1ste4biuk2-1
  bzrlib/tests/blackbox/test_merge_directive.py test_merge_directive-20070302012039-zh7uhy39biairtn0-1
  bzrlib/tests/test_smtp_connection.py test_smtp_connection-20070618204509-wuyxc0r0ztrecv7e-1
    ------------------------------------------------------------
    revno: 2898.2.4
    merged: james at jamesh.id.au-20071020003544-ol0k71n7vgefhlwd
    parent: james at jamesh.id.au-20071020001437-pbujo03uyui6idz4
    parent: pqm at pqm.ubuntu.com-20071019201226-6z006xotgfe7zmu8
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr.smtp
    timestamp: Sat 2007-10-20 08:35:44 +0800
    message:
      merge from bzr.dev
    ------------------------------------------------------------
    revno: 2898.2.3
    merged: james at jamesh.id.au-20071020001437-pbujo03uyui6idz4
    parent: james at jamesh.id.au-20071010034304-pnj843bv68xy151f
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr.smtp
    timestamp: Sat 2007-10-20 08:14:37 +0800
    message:
      * Fix merge-directive blackbox test.
      * Fix small typo in comment in smtp_connection.py
    ------------------------------------------------------------
    revno: 2898.2.2
    merged: james at jamesh.id.au-20071010034304-pnj843bv68xy151f
    parent: james at jamesh.id.au-20071009104548-ygqxbh7322f1jfem
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr.smtp
    timestamp: Wed 2007-10-10 16:43:04 +1300
    message:
      Fix test helper class naming, per John's review comments.
    ------------------------------------------------------------
    revno: 2898.2.1
    merged: james at jamesh.id.au-20071009104548-ygqxbh7322f1jfem
    parent: pqm at pqm.ubuntu.com-20071009044446-uliu5z9a52bzmps8
    committer: James Henstridge <james at jamesh.id.au>
    branch nick: bzr.smtp
    timestamp: Tue 2007-10-09 23:45:48 +1300
    message:
      Update SMTPConnection._create_connection to better follow the SMTP 
      specification (and hence work with fussy servers):
       * Call ehlo/helo ourselves instead of relying on login() or sendmail() 
         calling them implicitly.
       * Only call starttls if the previous ehlo response indicates that the 
         server supports the extension.  If starttls fails, raise SMTPError.
       * If we call starttls, follow it with another ehlo to discover any 
         newly revealed extensions (e.g. auth).
      
      Also add some tests for _create_connection().
=== modified file 'bzrlib/smtp_connection.py'
--- a/bzrlib/smtp_connection.py	2007-08-10 16:17:30 +0000
+++ b/bzrlib/smtp_connection.py	2007-10-20 00:14:37 +0000
@@ -79,10 +79,22 @@
             else:
                 raise
 
-        # If this fails, it just returns an error, but it shouldn't raise an
-        # exception unless something goes really wrong (in which case we want
-        # to fail anyway).
-        self._connection.starttls()
+        # Say EHLO (falling back to HELO) to query the server's features.
+        code, resp = self._connection.ehlo()
+        if not (200 <= code <= 299):
+            code, resp = self._connection.helo()
+            if not (200 <= code <= 299):
+                raise SMTPError("server refused HELO: %d %s" % (code, resp))
+
+        # Use TLS if the server advertised it:
+        if self._connection.has_extn("starttls"):
+            code, resp = self._connection.starttls()
+            if not (200 <= code <= 299):
+                raise SMTPError("server refused STARTTLS: %d %s" % (code, resp))
+            # Say EHLO again, to check for newly revealed features
+            code, resp = self._connection.ehlo()
+            if not (200 <= code <= 299):
+                raise SMTPError("server refused EHLO: %d %s" % (code, resp))
 
     def _authenticate(self):
         """If necessary authenticate yourself to the server."""

=== modified file 'bzrlib/tests/blackbox/test_merge_directive.py'
--- a/bzrlib/tests/blackbox/test_merge_directive.py	2007-08-09 03:39:31 +0000
+++ b/bzrlib/tests/blackbox/test_merge_directive.py	2007-10-20 00:14:37 +0000
@@ -116,20 +116,25 @@
         connect_calls = []
         def connect(self, host='localhost', port=0):
             connect_calls.append((self, host, port))
-        def starttls(self):
-            pass
+        def has_extn(self, extension):
+            return False
+        def ehlo(self):
+            return (200, 'Ok')
         old_sendmail = smtplib.SMTP.sendmail
         smtplib.SMTP.sendmail = sendmail
         old_connect = smtplib.SMTP.connect
         smtplib.SMTP.connect = connect
-        old_starttls = smtplib.SMTP.starttls
-        smtplib.SMTP.starttls = starttls
+        old_ehlo = smtplib.SMTP.ehlo
+        smtplib.SMTP.ehlo = ehlo
+        old_has_extn = smtplib.SMTP.has_extn
+        smtplib.SMTP.has_extn = has_extn
         try:
             result = self.run_bzr(*args, **kwargs)
         finally:
             smtplib.SMTP.sendmail = old_sendmail
             smtplib.SMTP.connect = old_connect
-            smtplib.SMTP.starttls = old_starttls
+            smtplib.SMTP.ehlo = old_ehlo
+            smtplib.SMTP.has_extn = old_has_extn
         return result + (connect_calls, sendmail_calls)
 
     def test_mail_default(self):

=== modified file 'bzrlib/tests/test_smtp_connection.py'
--- a/bzrlib/tests/test_smtp_connection.py	2007-08-10 16:17:30 +0000
+++ b/bzrlib/tests/test_smtp_connection.py	2007-10-10 03:43:04 +0000
@@ -38,6 +38,49 @@
     return smtp
 
 
+class StubSMTPFactory(object):
+    """A fake SMTP connection to test the connection setup."""
+    def __init__(self, fail_on=None, smtp_features=None):
+        self._fail_on = fail_on or []
+        self._calls = []
+        self._smtp_features = smtp_features or []
+        self._ehlo_called = False
+
+    def __call__(self):
+        # The factory pretends to be a connection
+        return self
+
+    def connect(self, server):
+        self._calls.append(('connect', server))
+
+    def helo(self):
+        self._calls.append(('helo',))
+        if 'helo' in self._fail_on:
+            return 500, 'helo failure'
+        else:
+            return 200, 'helo success'
+
+    def ehlo(self):
+        self._calls.append(('ehlo',))
+        if 'ehlo' in self._fail_on:
+            return 500, 'ehlo failure'
+        else:
+            self._ehlo_called = True
+            return 200, 'ehlo success'
+
+    def has_extn(self, extension):
+        self._calls.append(('has_extn', extension))
+        return self._ehlo_called and extension in self._smtp_features
+
+    def starttls(self):
+        self._calls.append(('starttls',))
+        if 'starttls' in self._fail_on:
+            return 500, 'starttls failure'
+        else:
+            self._ehlo_called = True
+            return 200, 'starttls success'
+
+
 class TestSMTPConnection(TestCase):
 
     def get_connection(self, text, smtp_factory=None):
@@ -77,6 +120,57 @@
         conn = self.get_connection('[DEFAULT]\nsmtp_password=mypass\n')
         self.assertEqual(u'mypass', conn._smtp_password)
 
+    def test_create_connection(self):
+        factory = StubSMTPFactory()
+        conn = self.get_connection('', smtp_factory=factory)
+        conn._create_connection()
+        self.assertEqual([('connect', 'localhost'),
+                          ('ehlo',),
+                          ('has_extn', 'starttls')], factory._calls)
+
+    def test_create_connection_ehlo_fails(self):
+        # Check that we call HELO if EHLO failed.
+        factory = StubSMTPFactory(fail_on=['ehlo'])
+        conn = self.get_connection('', smtp_factory=factory)
+        conn._create_connection()
+        self.assertEqual([('connect', 'localhost'),
+                          ('ehlo',),
+                          ('helo',),
+                          ('has_extn', 'starttls')], factory._calls)
+
+    def test_create_connection_ehlo_helo_fails(self):
+        # Check that we raise an exception if both EHLO and HELO fail.
+        factory = StubSMTPFactory(fail_on=['ehlo', 'helo'])
+        conn = self.get_connection('', smtp_factory=factory)
+        self.assertRaises(errors.SMTPError, conn._create_connection)
+        self.assertEqual([('connect', 'localhost'),
+                          ('ehlo',),
+                          ('helo',)], factory._calls)
+
+    def test_create_connection_starttls(self):
+        # Check that STARTTLS plus a second EHLO are called if the
+        # server says it supports the feature.
+        factory = StubSMTPFactory(smtp_features=['starttls'])
+        conn = self.get_connection('', smtp_factory=factory)
+        conn._create_connection()
+        self.assertEqual([('connect', 'localhost'),
+                          ('ehlo',),
+                          ('has_extn', 'starttls'),
+                          ('starttls',),
+                          ('ehlo',)], factory._calls)
+
+    def test_create_connection_starttls_fails(self):
+        # Check that we raise an exception if the server claims to
+        # support STARTTLS, but then fails when we try to activate it.
+        factory = StubSMTPFactory(fail_on=['starttls'],
+                                  smtp_features=['starttls'])
+        conn = self.get_connection('', smtp_factory=factory)
+        self.assertRaises(errors.SMTPError, conn._create_connection)
+        self.assertEqual([('connect', 'localhost'),
+                          ('ehlo',),
+                          ('has_extn', 'starttls'),
+                          ('starttls',)], factory._calls)
+
     def test_get_message_addresses(self):
         msg = Message()
 




More information about the bazaar-commits mailing list