1 | diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
|
---|
2 | index bf0dea5..19258fb 100644
|
---|
3 | --- a/django/conf/global_settings.py
|
---|
4 | +++ b/django/conf/global_settings.py
|
---|
5 | @@ -184,6 +184,7 @@ EMAIL_PORT = 25
|
---|
6 | EMAIL_HOST_USER = ''
|
---|
7 | EMAIL_HOST_PASSWORD = ''
|
---|
8 | EMAIL_USE_TLS = False
|
---|
9 | +EMAIL_USE_SSL = False
|
---|
10 |
|
---|
11 | # List of strings representing installed apps.
|
---|
12 | INSTALLED_APPS = ()
|
---|
13 | diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py
|
---|
14 | index e456b78..fc77080 100644
|
---|
15 | --- a/django/core/mail/backends/smtp.py
|
---|
16 | +++ b/django/core/mail/backends/smtp.py
|
---|
17 | @@ -15,7 +15,7 @@ class EmailBackend(BaseEmailBackend):
|
---|
18 | A wrapper that manages the SMTP network connection.
|
---|
19 | """
|
---|
20 | def __init__(self, host=None, port=None, username=None, password=None,
|
---|
21 | - use_tls=None, fail_silently=False, **kwargs):
|
---|
22 | + use_tls=None, fail_silently=False, use_ssl=None, **kwargs):
|
---|
23 | super(EmailBackend, self).__init__(fail_silently=fail_silently)
|
---|
24 | self.host = host or settings.EMAIL_HOST
|
---|
25 | self.port = port or settings.EMAIL_PORT
|
---|
26 | @@ -31,6 +31,13 @@ class EmailBackend(BaseEmailBackend):
|
---|
27 | self.use_tls = settings.EMAIL_USE_TLS
|
---|
28 | else:
|
---|
29 | self.use_tls = use_tls
|
---|
30 | + if use_ssl is None:
|
---|
31 | + self.use_ssl = settings.EMAIL_USE_SSL
|
---|
32 | + else:
|
---|
33 | + self.use_ssl = use_ssl
|
---|
34 | + if self.use_ssl and self.use_tls:
|
---|
35 | + raise ValueError(
|
---|
36 | + "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set one of those settings to True.")
|
---|
37 | self.connection = None
|
---|
38 | self._lock = threading.RLock()
|
---|
39 |
|
---|
40 | @@ -45,12 +52,18 @@ class EmailBackend(BaseEmailBackend):
|
---|
41 | try:
|
---|
42 | # If local_hostname is not specified, socket.getfqdn() gets used.
|
---|
43 | # For performance, we use the cached FQDN for local_hostname.
|
---|
44 | - self.connection = smtplib.SMTP(self.host, self.port,
|
---|
45 | + if self.use_ssl:
|
---|
46 | + self.connection = smtplib.SMTP_SSL(self.host, self.port,
|
---|
47 | + local_hostname=DNS_NAME.get_fqdn())
|
---|
48 | + else:
|
---|
49 | + self.connection = smtplib.SMTP(self.host, self.port,
|
---|
50 | local_hostname=DNS_NAME.get_fqdn())
|
---|
51 | - if self.use_tls:
|
---|
52 | - self.connection.ehlo()
|
---|
53 | - self.connection.starttls()
|
---|
54 | - self.connection.ehlo()
|
---|
55 | + # TLS/SSL are mutually exclusive, so only attempt TLS over
|
---|
56 | + # non-secure connections.
|
---|
57 | + if self.use_tls:
|
---|
58 | + self.connection.ehlo()
|
---|
59 | + self.connection.starttls()
|
---|
60 | + self.connection.ehlo()
|
---|
61 | if self.username and self.password:
|
---|
62 | self.connection.login(self.username, self.password)
|
---|
63 | return True
|
---|
64 | diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
|
---|
65 | index 30e7e84..350e8ec 100644
|
---|
66 | --- a/docs/ref/settings.txt
|
---|
67 | +++ b/docs/ref/settings.txt
|
---|
68 | @@ -1055,6 +1055,26 @@ EMAIL_USE_TLS
|
---|
69 | Default: ``False``
|
---|
70 |
|
---|
71 | Whether to use a TLS (secure) connection when talking to the SMTP server.
|
---|
72 | +This is used for explicit TLS connections, generally on port 587. If you are
|
---|
73 | +experiencing hanging connections, see the implicit TLS setting
|
---|
74 | +:setting:`EMAIL_USE_SSL`.
|
---|
75 | +
|
---|
76 | +.. setting:: EMAIL_USE_SSL
|
---|
77 | +
|
---|
78 | +EMAIL_USE_SSL
|
---|
79 | +-------------
|
---|
80 | +
|
---|
81 | +.. versionadded:: 1.6
|
---|
82 | +
|
---|
83 | +Default: ``False``
|
---|
84 | +
|
---|
85 | +Whether to use an implicit TLS (secure) connection when talking to the SMTP
|
---|
86 | +server. In most email documentation this type of TLS connection is referred
|
---|
87 | +to as SSL. It is generally used on port 465. If you are experiencing problems,
|
---|
88 | +see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
|
---|
89 | +
|
---|
90 | +Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
|
---|
91 | +exclusive, so only set one of those settings to ``True``.
|
---|
92 |
|
---|
93 | .. setting:: FILE_CHARSET
|
---|
94 |
|
---|
95 | diff --git a/docs/topics/email.txt b/docs/topics/email.txt
|
---|
96 | index b3d7254..8bf501c 100644
|
---|
97 | --- a/docs/topics/email.txt
|
---|
98 | +++ b/docs/topics/email.txt
|
---|
99 | @@ -27,7 +27,8 @@ Mail is sent using the SMTP host and port specified in the
|
---|
100 | :setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
|
---|
101 | :setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
|
---|
102 | set, are used to authenticate to the SMTP server, and the
|
---|
103 | -:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
|
---|
104 | +:setting:`EMAIL_USE_TLS` and :setting:`EMAIL_USE_SSL` settings control whether
|
---|
105 | +a secure connection is used.
|
---|
106 |
|
---|
107 | .. note::
|
---|
108 |
|
---|
109 | @@ -408,8 +409,8 @@ SMTP backend
|
---|
110 | This is the default backend. Email will be sent through a SMTP server.
|
---|
111 | The server address and authentication credentials are set in the
|
---|
112 | :setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
|
---|
113 | -:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
|
---|
114 | -settings file.
|
---|
115 | +:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
|
---|
116 | +:setting:`EMAIL_USE_SSL` settings in your settings file.
|
---|
117 |
|
---|
118 | The SMTP backend is the default configuration inherited by Django. If you
|
---|
119 | want to specify it explicitly, put the following in your settings::
|
---|
120 | diff --git a/tests/mail/tests.py b/tests/mail/tests.py
|
---|
121 | index c90dc7e..822c572 100644
|
---|
122 | --- a/tests/mail/tests.py
|
---|
123 | +++ b/tests/mail/tests.py
|
---|
124 | @@ -9,6 +9,8 @@ import smtpd
|
---|
125 | import sys
|
---|
126 | import tempfile
|
---|
127 | import threading
|
---|
128 | +from smtplib import SMTPException
|
---|
129 | +from ssl import SSLError
|
---|
130 |
|
---|
131 | from django.core import mail
|
---|
132 | from django.core.mail import (EmailMessage, mail_admins, mail_managers,
|
---|
133 | @@ -621,11 +623,23 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
|
---|
134 | 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: '))
|
---|
135 |
|
---|
136 |
|
---|
137 | +class FakeSMTPChannel(smtpd.SMTPChannel):
|
---|
138 | +
|
---|
139 | + def collect_incoming_data(self, data):
|
---|
140 | + try:
|
---|
141 | + super(FakeSMTPChannel, self).collect_incoming_data(data)
|
---|
142 | + except UnicodeDecodeError:
|
---|
143 | + # ignore decode error in SSL/TLS connection tests as we only care
|
---|
144 | + # whether the connection attempt was made
|
---|
145 | + pass
|
---|
146 | +
|
---|
147 | +
|
---|
148 | class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
|
---|
149 | """
|
---|
150 | Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
|
---|
151 | http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
|
---|
152 | """
|
---|
153 | + channel_class = FakeSMTPChannel
|
---|
154 |
|
---|
155 | def __init__(self, *args, **kwargs):
|
---|
156 | threading.Thread.__init__(self)
|
---|
157 | @@ -738,3 +752,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
|
---|
158 | backend.close()
|
---|
159 | except Exception as e:
|
---|
160 | self.fail("close() unexpectedly raised an exception: %s" % e)
|
---|
161 | +
|
---|
162 | + @override_settings(EMAIL_USE_TLS=True)
|
---|
163 | + def test_email_tls_use_settings(self):
|
---|
164 | + backend = smtp.EmailBackend()
|
---|
165 | + self.assertTrue(backend.use_tls)
|
---|
166 | +
|
---|
167 | + @override_settings(EMAIL_USE_TLS=True)
|
---|
168 | + def test_email_tls_override_settings(self):
|
---|
169 | + backend = smtp.EmailBackend(use_tls=False)
|
---|
170 | + self.assertFalse(backend.use_tls)
|
---|
171 | +
|
---|
172 | + def test_email_tls_default_disabled(self):
|
---|
173 | + backend = smtp.EmailBackend()
|
---|
174 | + self.assertFalse(backend.use_tls)
|
---|
175 | +
|
---|
176 | + @override_settings(EMAIL_USE_SSL=True)
|
---|
177 | + def test_email_ssl_use_settings(self):
|
---|
178 | + backend = smtp.EmailBackend()
|
---|
179 | + self.assertTrue(backend.use_ssl)
|
---|
180 | +
|
---|
181 | + @override_settings(EMAIL_USE_SSL=True)
|
---|
182 | + def test_email_ssl_override_settings(self):
|
---|
183 | + backend = smtp.EmailBackend(use_ssl=False)
|
---|
184 | + self.assertFalse(backend.use_ssl)
|
---|
185 | +
|
---|
186 | + def test_email_ssl_default_disabled(self):
|
---|
187 | + backend = smtp.EmailBackend()
|
---|
188 | + self.assertFalse(backend.use_ssl)
|
---|
189 | +
|
---|
190 | + @override_settings(EMAIL_USE_TLS=True)
|
---|
191 | + def test_email_tls_attempts_starttls(self):
|
---|
192 | + backend = smtp.EmailBackend()
|
---|
193 | + self.assertTrue(backend.use_tls)
|
---|
194 | + self.assertRaisesMessage(SMTPException,
|
---|
195 | + 'STARTTLS extension not supported by server.', backend.open)
|
---|
196 | +
|
---|
197 | + @override_settings(EMAIL_USE_SSL=True)
|
---|
198 | + def test_email_ssl_attempts_ssl_connection(self):
|
---|
199 | + backend = smtp.EmailBackend()
|
---|
200 | + self.assertTrue(backend.use_ssl)
|
---|
201 | + self.assertRaises(SSLError, backend.open)
|
---|