Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #17471 -- Added smtplib.SMTP_SSL connection option for SMTP bac…

…kend

Thanks dj.facebook at gmail.com for the report and initial patch
and Areski Belaid and senko for improvements.
  • Loading branch information...
commit 59ebe39812858ba37e83ab0ee886f676980d472d 1 parent 684a606
Claude Paroz authored July 11, 2013
1  django/conf/global_settings.py
@@ -184,6 +184,7 @@
184 184
 EMAIL_HOST_USER = ''
185 185
 EMAIL_HOST_PASSWORD = ''
186 186
 EMAIL_USE_TLS = False
  187
+EMAIL_USE_SSL = False
187 188
 
188 189
 # List of strings representing installed apps.
189 190
 INSTALLED_APPS = ()
38  django/core/mail/backends/smtp.py
@@ -15,22 +15,18 @@ class EmailBackend(BaseEmailBackend):
15 15
     A wrapper that manages the SMTP network connection.
16 16
     """
17 17
     def __init__(self, host=None, port=None, username=None, password=None,
18  
-                 use_tls=None, fail_silently=False, **kwargs):
  18
+                 use_tls=None, fail_silently=False, use_ssl=None, **kwargs):
19 19
         super(EmailBackend, self).__init__(fail_silently=fail_silently)
20 20
         self.host = host or settings.EMAIL_HOST
21 21
         self.port = port or settings.EMAIL_PORT
22  
-        if username is None:
23  
-            self.username = settings.EMAIL_HOST_USER
24  
-        else:
25  
-            self.username = username
26  
-        if password is None:
27  
-            self.password = settings.EMAIL_HOST_PASSWORD
28  
-        else:
29  
-            self.password = password
30  
-        if use_tls is None:
31  
-            self.use_tls = settings.EMAIL_USE_TLS
32  
-        else:
33  
-            self.use_tls = use_tls
  22
+        self.username = settings.EMAIL_HOST_USER if username is None else username
  23
+        self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
  24
+        self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
  25
+        self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
  26
+        if self.use_ssl and self.use_tls:
  27
+            raise ValueError(
  28
+                "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
  29
+                "one of those settings to True.")
34 30
         self.connection = None
35 31
         self._lock = threading.RLock()
36 32
 
@@ -45,12 +41,18 @@ def open(self):
45 41
         try:
46 42
             # If local_hostname is not specified, socket.getfqdn() gets used.
47 43
             # For performance, we use the cached FQDN for local_hostname.
48  
-            self.connection = smtplib.SMTP(self.host, self.port,
  44
+            if self.use_ssl:
  45
+                self.connection = smtplib.SMTP_SSL(self.host, self.port,
49 46
                                            local_hostname=DNS_NAME.get_fqdn())
50  
-            if self.use_tls:
51  
-                self.connection.ehlo()
52  
-                self.connection.starttls()
53  
-                self.connection.ehlo()
  47
+            else:
  48
+                self.connection = smtplib.SMTP(self.host, self.port,
  49
+                                           local_hostname=DNS_NAME.get_fqdn())
  50
+                # TLS/SSL are mutually exclusive, so only attempt TLS over
  51
+                # non-secure connections.
  52
+                if self.use_tls:
  53
+                    self.connection.ehlo()
  54
+                    self.connection.starttls()
  55
+                    self.connection.ehlo()
54 56
             if self.username and self.password:
55 57
                 self.connection.login(self.username, self.password)
56 58
             return True
20  docs/ref/settings.txt
@@ -1055,6 +1055,26 @@ EMAIL_USE_TLS
1055 1055
 Default: ``False``
1056 1056
 
1057 1057
 Whether to use a TLS (secure) connection when talking to the SMTP server.
  1058
+This is used for explicit TLS connections, generally on port 587. If you are
  1059
+experiencing hanging connections, see the implicit TLS setting
  1060
+:setting:`EMAIL_USE_SSL`.
  1061
+
  1062
+.. setting:: EMAIL_USE_SSL
  1063
+
  1064
+EMAIL_USE_SSL
  1065
+-------------
  1066
+
  1067
+.. versionadded:: 1.7
  1068
+
  1069
+Default: ``False``
  1070
+
  1071
+Whether to use an implicit TLS (secure) connection when talking to the SMTP
  1072
+server. In most email documentation this type of TLS connection is referred
  1073
+to as SSL. It is generally used on port 465. If you are experiencing problems,
  1074
+see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
  1075
+
  1076
+Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
  1077
+exclusive, so only set one of those settings to ``True``.
1058 1078
 
1059 1079
 .. setting:: FILE_CHARSET
1060 1080
 
7  docs/topics/email.txt
@@ -27,7 +27,8 @@ Mail is sent using the SMTP host and port specified in the
27 27
 :setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
28 28
 :setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
29 29
 set, are used to authenticate to the SMTP server, and the
30  
-:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
  30
+:setting:`EMAIL_USE_TLS` and :setting:`EMAIL_USE_SSL` settings control whether
  31
+a secure connection is used.
31 32
 
32 33
 .. note::
33 34
 
@@ -408,8 +409,8 @@ SMTP backend
408 409
 This is the default backend. Email will be sent through a SMTP server.
409 410
 The server address and authentication credentials are set in the
410 411
 :setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
411  
-:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
412  
-settings file.
  412
+:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
  413
+:setting:`EMAIL_USE_SSL` settings in your settings file.
413 414
 
414 415
 The SMTP backend is the default configuration inherited by Django. If you
415 416
 want to specify it explicitly, put the following in your settings::
55  tests/mail/tests.py
@@ -9,6 +9,8 @@
9 9
 import sys
10 10
 import tempfile
11 11
 import threading
  12
+from smtplib import SMTPException
  13
+from ssl import SSLError
12 14
 
13 15
 from django.core import mail
14 16
 from django.core.mail import (EmailMessage, mail_admins, mail_managers,
@@ -621,11 +623,23 @@ def test_console_stream_kwarg(self):
621 623
         self.assertTrue(s.getvalue().startswith('Content-Type: text/plain; charset="utf-8"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nSubject: Subject\nFrom: from@example.com\nTo: to@example.com\nDate: '))
622 624
 
623 625
 
  626
+class FakeSMTPChannel(smtpd.SMTPChannel):
  627
+
  628
+    def collect_incoming_data(self, data):
  629
+        try:
  630
+            super(FakeSMTPChannel, self).collect_incoming_data(data)
  631
+        except UnicodeDecodeError:
  632
+            # ignore decode error in SSL/TLS connection tests as we only care
  633
+            # whether the connection attempt was made
  634
+            pass
  635
+
  636
+
624 637
 class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
625 638
     """
626 639
     Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
627 640
     http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
628 641
     """
  642
+    channel_class = FakeSMTPChannel
629 643
 
630 644
     def __init__(self, *args, **kwargs):
631 645
         threading.Thread.__init__(self)
@@ -738,3 +752,44 @@ def test_server_stopped(self):
738 752
             backend.close()
739 753
         except Exception as e:
740 754
             self.fail("close() unexpectedly raised an exception: %s" % e)
  755
+
  756
+    @override_settings(EMAIL_USE_TLS=True)
  757
+    def test_email_tls_use_settings(self):
  758
+        backend = smtp.EmailBackend()
  759
+        self.assertTrue(backend.use_tls)
  760
+
  761
+    @override_settings(EMAIL_USE_TLS=True)
  762
+    def test_email_tls_override_settings(self):
  763
+        backend = smtp.EmailBackend(use_tls=False)
  764
+        self.assertFalse(backend.use_tls)
  765
+
  766
+    def test_email_tls_default_disabled(self):
  767
+        backend = smtp.EmailBackend()
  768
+        self.assertFalse(backend.use_tls)
  769
+
  770
+    @override_settings(EMAIL_USE_SSL=True)
  771
+    def test_email_ssl_use_settings(self):
  772
+        backend = smtp.EmailBackend()
  773
+        self.assertTrue(backend.use_ssl)
  774
+
  775
+    @override_settings(EMAIL_USE_SSL=True)
  776
+    def test_email_ssl_override_settings(self):
  777
+        backend = smtp.EmailBackend(use_ssl=False)
  778
+        self.assertFalse(backend.use_ssl)
  779
+
  780
+    def test_email_ssl_default_disabled(self):
  781
+        backend = smtp.EmailBackend()
  782
+        self.assertFalse(backend.use_ssl)
  783
+
  784
+    @override_settings(EMAIL_USE_TLS=True)
  785
+    def test_email_tls_attempts_starttls(self):
  786
+        backend = smtp.EmailBackend()
  787
+        self.assertTrue(backend.use_tls)
  788
+        self.assertRaisesMessage(SMTPException,
  789
+            'STARTTLS extension not supported by server.', backend.open)
  790
+
  791
+    @override_settings(EMAIL_USE_SSL=True)
  792
+    def test_email_ssl_attempts_ssl_connection(self):
  793
+        backend = smtp.EmailBackend()
  794
+        self.assertTrue(backend.use_ssl)
  795
+        self.assertRaises(SSLError, backend.open)

0 notes on commit 59ebe39

Please sign in to comment.
Something went wrong with that request. Please try again.