Browse Source

Fixed #20743 -- Added support for keyfile/certfile in SMTP connections.

Thanks jwmayfield, serg.partizan, and Wojciech Banaś for work on the patch.
Andi Albrecht 11 years ago
parent
commit
00535e8e6b

+ 2 - 0
django/conf/global_settings.py

@@ -194,6 +194,8 @@ EMAIL_HOST_USER = ''
 EMAIL_HOST_PASSWORD = ''
 EMAIL_HOST_PASSWORD = ''
 EMAIL_USE_TLS = False
 EMAIL_USE_TLS = False
 EMAIL_USE_SSL = False
 EMAIL_USE_SSL = False
+EMAIL_SSL_CERTFILE = None
+EMAIL_SSL_KEYFILE = None
 
 
 # List of strings representing installed apps.
 # List of strings representing installed apps.
 INSTALLED_APPS = ()
 INSTALLED_APPS = ()

+ 9 - 1
django/core/mail/backends/smtp.py

@@ -15,6 +15,7 @@ class EmailBackend(BaseEmailBackend):
     """
     """
     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, use_ssl=None, timeout=None,
                  use_tls=None, fail_silently=False, use_ssl=None, timeout=None,
+                 ssl_keyfile=None, ssl_certfile=None,
                  **kwargs):
                  **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
@@ -24,6 +25,8 @@ class EmailBackend(BaseEmailBackend):
         self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
         self.use_tls = settings.EMAIL_USE_TLS if use_tls is None else use_tls
         self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
         self.use_ssl = settings.EMAIL_USE_SSL if use_ssl is None else use_ssl
         self.timeout = timeout
         self.timeout = timeout
+        self.ssl_keyfile = settings.EMAIL_SSL_KEYFILE if ssl_keyfile is None else ssl_keyfile
+        self.ssl_certfile = settings.EMAIL_SSL_CERTFILE if ssl_certfile is None else ssl_certfile
         if self.use_ssl and self.use_tls:
         if self.use_ssl and self.use_tls:
             raise ValueError(
             raise ValueError(
                 "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
                 "EMAIL_USE_TLS/EMAIL_USE_SSL are mutually exclusive, so only set "
@@ -46,6 +49,11 @@ class EmailBackend(BaseEmailBackend):
         connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
         connection_params = {'local_hostname': DNS_NAME.get_fqdn()}
         if self.timeout is not None:
         if self.timeout is not None:
             connection_params['timeout'] = self.timeout
             connection_params['timeout'] = self.timeout
+        if self.use_ssl:
+            connection_params.update({
+                'keyfile': self.ssl_keyfile,
+                'certfile': self.ssl_certfile,
+            })
         try:
         try:
             self.connection = connection_class(self.host, self.port, **connection_params)
             self.connection = connection_class(self.host, self.port, **connection_params)
 
 
@@ -53,7 +61,7 @@ class EmailBackend(BaseEmailBackend):
             # non-secure connections.
             # non-secure connections.
             if not self.use_ssl and self.use_tls:
             if not self.use_ssl and self.use_tls:
                 self.connection.ehlo()
                 self.connection.ehlo()
-                self.connection.starttls()
+                self.connection.starttls(keyfile=self.ssl_keyfile, certfile=self.ssl_certfile)
                 self.connection.ehlo()
                 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)

+ 34 - 0
docs/ref/settings.txt

@@ -1228,6 +1228,38 @@ see the explicit TLS setting :setting:`EMAIL_USE_TLS`.
 Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
 Note that :setting:`EMAIL_USE_TLS`/:setting:`EMAIL_USE_SSL` are mutually
 exclusive, so only set one of those settings to ``True``.
 exclusive, so only set one of those settings to ``True``.
 
 
+.. setting:: EMAIL_SSL_CERTFILE
+
+EMAIL_SSL_CERTFILE
+------------------
+
+.. versionadded:: 1.8
+
+Default: ``None``
+
+If :setting:`EMAIL_USE_SSL` or :setting:`EMAIL_USE_TLS` is ``True``, you can
+optionally specify the path to a PEM-formatted certificate chain file to use
+for the SSL connection.
+
+.. setting:: EMAIL_SSL_KEYFILE
+
+EMAIL_SSL_KEYFILE
+-----------------
+
+.. versionadded:: 1.8
+
+Default: ``None``
+
+If :setting:`EMAIL_USE_SSL` or :setting:`EMAIL_USE_TLS` is ``True``, you can
+optionally specify the path to a PEM-formatted private key file to use for the
+SSL connection.
+
+Note that setting :setting:`EMAIL_SSL_CERTFILE` and :setting:`EMAIL_SSL_KEYFILE`
+doesn't result in any certificate checking. They're passed to the underlying SSL
+connection. Please refer to the documentation of Python's
+:func:`python:ssl.wrap_socket` function for details on how the certificate chain
+file and private key file are handled.
+
 .. setting:: FILE_CHARSET
 .. setting:: FILE_CHARSET
 
 
 FILE_CHARSET
 FILE_CHARSET
@@ -2926,6 +2958,8 @@ Email
 * :setting:`EMAIL_HOST_PASSWORD`
 * :setting:`EMAIL_HOST_PASSWORD`
 * :setting:`EMAIL_HOST_USER`
 * :setting:`EMAIL_HOST_USER`
 * :setting:`EMAIL_PORT`
 * :setting:`EMAIL_PORT`
+* :setting:`EMAIL_SSL_CERTFILE`
+* :setting:`EMAIL_SSL_KEYFILE`
 * :setting:`EMAIL_SUBJECT_PREFIX`
 * :setting:`EMAIL_SUBJECT_PREFIX`
 * :setting:`EMAIL_USE_TLS`
 * :setting:`EMAIL_USE_TLS`
 * :setting:`MANAGERS`
 * :setting:`MANAGERS`

+ 4 - 0
docs/releases/1.8.txt

@@ -140,6 +140,10 @@ Email
 * :ref:`Email backends <topic-email-backends>` now support the context manager
 * :ref:`Email backends <topic-email-backends>` now support the context manager
   protocol for opening and closing connections.
   protocol for opening and closing connections.
 
 
+* The SMTP email backend now supports ``keyfile`` and ``certfile``
+  authentication with the :setting:`EMAIL_SSL_CERTFILE` and
+  :setting:`EMAIL_SSL_KEYFILE` settings.
+
 File Storage
 File Storage
 ^^^^^^^^^^^^
 ^^^^^^^^^^^^
 
 

+ 1 - 0
docs/spelling_wordlist

@@ -426,6 +426,7 @@ Palau
 params
 params
 parens
 parens
 pdf
 pdf
+PEM
 perl
 perl
 permalink
 permalink
 pessimization
 pessimization

+ 9 - 3
docs/topics/email.txt

@@ -445,13 +445,14 @@ can :ref:`write your own email backend <topic-custom-email-backend>`.
 SMTP backend
 SMTP backend
 ~~~~~~~~~~~~
 ~~~~~~~~~~~~
 
 
-.. class:: backends.smtp.EmailBackend([host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, **kwargs])
+.. class:: backends.smtp.EmailBackend([host=None, port=None, username=None, password=None, use_tls=None, fail_silently=False, use_ssl=None, timeout=None, ssl_keyfile=None, ssl_certfile=None, **kwargs])
 
 
     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`, :setting:`EMAIL_USE_TLS` and
-    :setting:`EMAIL_USE_SSL` settings in your settings file.
+    :setting:`EMAIL_HOST_PASSWORD`, :setting:`EMAIL_USE_TLS`,
+    :setting:`EMAIL_USE_SSL`, :setting:`EMAIL_SSL_CERTFILE` and
+    :setting:`EMAIL_SSL_KEYFILE` 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::
@@ -481,6 +482,11 @@ SMTP backend
         If unspecified, the default ``timeout`` will be the one provided by
         If unspecified, the default ``timeout`` will be the one provided by
         :func:`socket.getdefaulttimeout()`, which defaults to ``None`` (no timeout).
         :func:`socket.getdefaulttimeout()`, which defaults to ``None`` (no timeout).
 
 
+    .. versionchanged:: 1.8
+
+        The ``ssl_keyfile`` and ``ssl_certfile`` parameters and
+        corresponding settings were added.
+
 .. _topic-email-console-backend:
 .. _topic-email-console-backend:
 
 
 Console backend
 Console backend

+ 28 - 0
tests/mail/tests.py

@@ -969,6 +969,34 @@ class SMTPBackendTests(BaseEmailBackendTests, SimpleTestCase):
         backend = smtp.EmailBackend()
         backend = smtp.EmailBackend()
         self.assertFalse(backend.use_ssl)
         self.assertFalse(backend.use_ssl)
 
 
+    @override_settings(EMAIL_SSL_CERTFILE='foo')
+    def test_email_ssl_certfile_use_settings(self):
+        backend = smtp.EmailBackend()
+        self.assertEqual(backend.ssl_certfile, 'foo')
+
+    @override_settings(EMAIL_SSL_CERTFILE='foo')
+    def test_email_ssl_certfile_override_settings(self):
+        backend = smtp.EmailBackend(ssl_certfile='bar')
+        self.assertEqual(backend.ssl_certfile, 'bar')
+
+    def test_email_ssl_certfile_default_disabled(self):
+        backend = smtp.EmailBackend()
+        self.assertEqual(backend.ssl_certfile, None)
+
+    @override_settings(EMAIL_SSL_KEYFILE='foo')
+    def test_email_ssl_keyfile_use_settings(self):
+        backend = smtp.EmailBackend()
+        self.assertEqual(backend.ssl_keyfile, 'foo')
+
+    @override_settings(EMAIL_SSL_KEYFILE='foo')
+    def test_email_ssl_keyfile_override_settings(self):
+        backend = smtp.EmailBackend(ssl_keyfile='bar')
+        self.assertEqual(backend.ssl_keyfile, 'bar')
+
+    def test_email_ssl_keyfile_default_disabled(self):
+        backend = smtp.EmailBackend()
+        self.assertEqual(backend.ssl_keyfile, None)
+
     @override_settings(EMAIL_USE_TLS=True)
     @override_settings(EMAIL_USE_TLS=True)
     def test_email_tls_attempts_starttls(self):
     def test_email_tls_attempts_starttls(self):
         backend = smtp.EmailBackend()
         backend = smtp.EmailBackend()