diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 53aef35..9e25009 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -184,6 +184,7 @@ EMAIL_PORT = 25 EMAIL_HOST_USER = '' EMAIL_HOST_PASSWORD = '' EMAIL_USE_TLS = False +EMAIL_USE_SSL = False # List of strings representing installed apps. INSTALLED_APPS = () diff --git a/django/core/mail/backends/smtp.py b/django/core/mail/backends/smtp.py index e456b78..fc77080 100644 --- a/django/core/mail/backends/smtp.py +++ b/django/core/mail/backends/smtp.py @@ -15,7 +15,7 @@ class EmailBackend(BaseEmailBackend): A wrapper that manages the SMTP network connection. """ 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) self.host = host or settings.EMAIL_HOST self.port = port or settings.EMAIL_PORT @@ -31,6 +31,13 @@ class EmailBackend(BaseEmailBackend): self.use_tls = settings.EMAIL_USE_TLS else: self.use_tls = use_tls + if use_ssl is None: + self.use_ssl = settings.EMAIL_USE_SSL + else: + self.use_ssl = use_ssl + if self.use_ssl and self.use_tls: + raise ValueError( + "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set one of those settings to True.") self.connection = None self._lock = threading.RLock() @@ -45,12 +52,18 @@ class EmailBackend(BaseEmailBackend): try: # If local_hostname is not specified, socket.getfqdn() gets used. # 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()) + else: + self.connection = smtplib.SMTP(self.host, self.port, local_hostname=DNS_NAME.get_fqdn()) - if self.use_tls: - self.connection.ehlo() - self.connection.starttls() - 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: self.connection.login(self.username, self.password) return True diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index eb470cd..427ac99 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1047,6 +1047,26 @@ EMAIL_USE_TLS Default: ``False`` 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.6 + +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 diff --git a/docs/topics/email.txt b/docs/topics/email.txt index b3d7254..8bf501c 100644 --- a/docs/topics/email.txt +++ b/docs/topics/email.txt @@ -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_USER` and :setting:`EMAIL_HOST_PASSWORD` settings, if 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:: @@ -408,8 +409,8 @@ SMTP backend This is the default backend. Email will be sent through a SMTP server. The server address and authentication credentials are set in the :setting:`EMAIL_HOST`, :setting:`EMAIL_PORT`, :setting:`EMAIL_HOST_USER`, -:setting:`EMAIL_HOST_PASSWORD` and :setting:`EMAIL_USE_TLS` settings in your -settings file. +:setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS` and +:setting:`EMAIL_USE_SSL` settings in your settings file. The SMTP backend is the default configuration inherited by Django. If you want to specify it explicitly, put the following in your settings:: diff --git a/tests/mail/tests.py b/tests/mail/tests.py index 0a843db..8079181 100644 --- a/tests/mail/tests.py +++ b/tests/mail/tests.py @@ -9,6 +9,8 @@ import smtpd import sys import tempfile import threading +from smtplib import SMTPException +from ssl import SSLError from django.core import mail from django.core.mail import (EmailMessage, mail_admins, mail_managers, @@ -738,3 +740,44 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase): backend.close() except Exception as 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)