Fixed #17471 -- Added smtplib.SMTP_SSL connection option for SMTP backend
Thanks dj.facebook at gmail.com for the report and initial patch and Areski Belaid and senko for improvements.
This commit is contained in:
parent
684a606a4e
commit
59ebe39812
|
@ -184,6 +184,7 @@ EMAIL_PORT = 25
|
||||||
EMAIL_HOST_USER = ''
|
EMAIL_HOST_USER = ''
|
||||||
EMAIL_HOST_PASSWORD = ''
|
EMAIL_HOST_PASSWORD = ''
|
||||||
EMAIL_USE_TLS = False
|
EMAIL_USE_TLS = False
|
||||||
|
EMAIL_USE_SSL = False
|
||||||
|
|
||||||
# List of strings representing installed apps.
|
# List of strings representing installed apps.
|
||||||
INSTALLED_APPS = ()
|
INSTALLED_APPS = ()
|
||||||
|
|
|
@ -15,22 +15,18 @@ class EmailBackend(BaseEmailBackend):
|
||||||
A wrapper that manages the SMTP network connection.
|
A wrapper that manages the SMTP network connection.
|
||||||
"""
|
"""
|
||||||
def __init__(self, host=None, port=None, username=None, password=None,
|
def __init__(self, host=None, port=None, username=None, password=None,
|
||||||
use_tls=None, fail_silently=False, **kwargs):
|
use_tls=None, fail_silently=False, use_ssl=None, **kwargs):
|
||||||
super(EmailBackend, self).__init__(fail_silently=fail_silently)
|
super(EmailBackend, self).__init__(fail_silently=fail_silently)
|
||||||
self.host = host or settings.EMAIL_HOST
|
self.host = host or settings.EMAIL_HOST
|
||||||
self.port = port or settings.EMAIL_PORT
|
self.port = port or settings.EMAIL_PORT
|
||||||
if username is None:
|
self.username = settings.EMAIL_HOST_USER if username is None else username
|
||||||
self.username = settings.EMAIL_HOST_USER
|
self.password = settings.EMAIL_HOST_PASSWORD if password is None else password
|
||||||
else:
|
self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
|
||||||
self.username = username
|
self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
|
||||||
if password is None:
|
if self.use_ssl and self.use_tls:
|
||||||
self.password = settings.EMAIL_HOST_PASSWORD
|
raise ValueError(
|
||||||
else:
|
"EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
|
||||||
self.password = password
|
"one of those settings to True.")
|
||||||
if use_tls is None:
|
|
||||||
self.use_tls = settings.EMAIL_USE_TLS
|
|
||||||
else:
|
|
||||||
self.use_tls = use_tls
|
|
||||||
self.connection = None
|
self.connection = None
|
||||||
self._lock = threading.RLock()
|
self._lock = threading.RLock()
|
||||||
|
|
||||||
|
@ -45,12 +41,18 @@ class EmailBackend(BaseEmailBackend):
|
||||||
try:
|
try:
|
||||||
# If local_hostname is not specified, socket.getfqdn() gets used.
|
# If local_hostname is not specified, socket.getfqdn() gets used.
|
||||||
# For performance, we use the cached FQDN for local_hostname.
|
# For performance, we use the cached FQDN for local_hostname.
|
||||||
self.connection = smtplib.SMTP(self.host, self.port,
|
if self.use_ssl:
|
||||||
|
self.connection = smtplib.SMTP_SSL(self.host, self.port,
|
||||||
local_hostname=DNS_NAME.get_fqdn())
|
local_hostname=DNS_NAME.get_fqdn())
|
||||||
if self.use_tls:
|
else:
|
||||||
self.connection.ehlo()
|
self.connection = smtplib.SMTP(self.host, self.port,
|
||||||
self.connection.starttls()
|
local_hostname=DNS_NAME.get_fqdn())
|
||||||
self.connection.ehlo()
|
# TLS/SSL are mutually exclusive, so only attempt TLS over
|
||||||
|
# non-secure connections.
|
||||||
|
if self.use_tls:
|
||||||
|
self.connection.ehlo()
|
||||||
|
self.connection.starttls()
|
||||||
|
self.connection.ehlo()
|
||||||
if self.username and self.password:
|
if self.username and self.password:
|
||||||
self.connection.login(self.username, self.password)
|
self.connection.login(self.username, self.password)
|
||||||
return True
|
return True
|
||||||
|
|
|
@ -1055,6 +1055,26 @@ EMAIL_USE_TLS
|
||||||
Default: ``False``
|
Default: ``False``
|
||||||
|
|
||||||
Whether to use a TLS (secure) connection when talking to the SMTP server.
|
Whether to use a TLS (secure) connection when talking to the SMTP server.
|
||||||
|
This is used for explicit TLS connections, generally on port 587. If you are
|
||||||
|
experiencing hanging connections, see the implicit TLS setting
|
||||||
|
:setting:`EMAIL_USE_SSL`.
|
||||||
|
|
||||||
|
.. setting:: EMAIL_USE_SSL
|
||||||
|
|
||||||
|
EMAIL_USE_SSL
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. versionadded:: 1.7
|
||||||
|
|
||||||
|
Default: ``False``
|
||||||
|
|
||||||
|
Whether to use an implicit TLS (secure) connection when talking to the SMTP
|
||||||
|
server. In most email documentation this type of TLS connection is referred
|
||||||
|
to as SSL. It is generally used on port 465. If you are experiencing problems,
|
||||||
|
see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
|
||||||
|
|
||||||
|
Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
|
||||||
|
exclusive, so only set one of those settings to ``True``.
|
||||||
|
|
||||||
.. setting:: FILE_CHARSET
|
.. setting:: FILE_CHARSET
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,8 @@ Mail is sent using the SMTP host and port specified in the
|
||||||
:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
|
:setting:`EMAIL_HOST` and :setting:`EMAIL_PORT` settings. The
|
||||||
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
|
:setting:`EMAIL_HOST_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if
|
||||||
set, are used to authenticate to the SMTP server, and the
|
set, are used to authenticate to the SMTP server, and the
|
||||||
:setting:`EMAIL_USE_TLS` setting controls whether a secure connection is used.
|
:setting:`EMAIL_USE_TLS` and :setting:`EMAIL_USE_SSL` settings control whether
|
||||||
|
a secure connection is used.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
@ -408,8 +409,8 @@ SMTP backend
|
||||||
This is the default backend. Email will be sent through a SMTP server.
|
This is the default backend. Email will be sent through a SMTP server.
|
||||||
The server address and authentication credentials are set in the
|
The server address and authentication credentials are set in the
|
||||||
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
|
:setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`,
|
||||||
:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your
|
:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and
|
||||||
settings file.
|
:setting:`EMAIL_USE_SSL` settings in your settings file.
|
||||||
|
|
||||||
The SMTP backend is the default configuration inherited by Django. If you
|
The SMTP backend is the default configuration inherited by Django. If you
|
||||||
want to specify it explicitly, put the following in your settings::
|
want to specify it explicitly, put the following in your settings::
|
||||||
|
|
|
@ -9,6 +9,8 @@ import smtpd
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import threading
|
import threading
|
||||||
|
from smtplib import SMTPException
|
||||||
|
from ssl import SSLError
|
||||||
|
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.core.mail import (EmailMessage, mail_admins, mail_managers,
|
from django.core.mail import (EmailMessage, mail_admins, mail_managers,
|
||||||
|
@ -621,11 +623,23 @@ class ConsoleBackendTests(BaseEmailBackendTests, TestCase):
|
||||||
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: '))
|
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: '))
|
||||||
|
|
||||||
|
|
||||||
|
class FakeSMTPChannel(smtpd.SMTPChannel):
|
||||||
|
|
||||||
|
def collect_incoming_data(self, data):
|
||||||
|
try:
|
||||||
|
super(FakeSMTPChannel, self).collect_incoming_data(data)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# ignore decode error in SSL/TLS connection tests as we only care
|
||||||
|
# whether the connection attempt was made
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
|
class FakeSMTPServer(smtpd.SMTPServer, threading.Thread):
|
||||||
"""
|
"""
|
||||||
Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
|
Asyncore SMTP server wrapped into a thread. Based on DummyFTPServer from:
|
||||||
http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
|
http://svn.python.org/view/python/branches/py3k/Lib/test/test_ftplib.py?revision=86061&view=markup
|
||||||
"""
|
"""
|
||||||
|
channel_class = FakeSMTPChannel
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
|
@ -738,3 +752,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
|
||||||
backend.close()
|
backend.close()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.fail("close() unexpectedly raised an exception: %s" % e)
|
self.fail("close() unexpectedly raised an exception: %s" % e)
|
||||||
|
|
||||||
|
@override_settings(EMAIL_USE_TLS=True)
|
||||||
|
def test_email_tls_use_settings(self):
|
||||||
|
backend = smtp.EmailBackend()
|
||||||
|
self.assertTrue(backend.use_tls)
|
||||||
|
|
||||||
|
@override_settings(EMAIL_USE_TLS=True)
|
||||||
|
def test_email_tls_override_settings(self):
|
||||||
|
backend = smtp.EmailBackend(use_tls=False)
|
||||||
|
self.assertFalse(backend.use_tls)
|
||||||
|
|
||||||
|
def test_email_tls_default_disabled(self):
|
||||||
|
backend = smtp.EmailBackend()
|
||||||
|
self.assertFalse(backend.use_tls)
|
||||||
|
|
||||||
|
@override_settings(EMAIL_USE_SSL=True)
|
||||||
|
def test_email_ssl_use_settings(self):
|
||||||
|
backend = smtp.EmailBackend()
|
||||||
|
self.assertTrue(backend.use_ssl)
|
||||||
|
|
||||||
|
@override_settings(EMAIL_USE_SSL=True)
|
||||||
|
def test_email_ssl_override_settings(self):
|
||||||
|
backend = smtp.EmailBackend(use_ssl=False)
|
||||||
|
self.assertFalse(backend.use_ssl)
|
||||||
|
|
||||||
|
def test_email_ssl_default_disabled(self):
|
||||||
|
backend = smtp.EmailBackend()
|
||||||
|
self.assertFalse(backend.use_ssl)
|
||||||
|
|
||||||
|
@override_settings(EMAIL_USE_TLS=True)
|
||||||
|
def test_email_tls_attempts_starttls(self):
|
||||||
|
backend = smtp.EmailBackend()
|
||||||
|
self.assertTrue(backend.use_tls)
|
||||||
|
self.assertRaisesMessage(SMTPException,
|
||||||
|
'STARTTLS extension not supported by server.', backend.open)
|
||||||
|
|
||||||
|
@override_settings(EMAIL_USE_SSL=True)
|
||||||
|
def test_email_ssl_attempts_ssl_connection(self):
|
||||||
|
backend = smtp.EmailBackend()
|
||||||
|
self.assertTrue(backend.use_ssl)
|
||||||
|
self.assertRaises(SSLError, backend.open)
|
||||||
|
|
Loading…
Reference in New Issue