Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Support Implicit TLS for sending emails #13317

Merged
merged 6 commits into from
Jul 25, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/13317.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Support Implicit TLS for sending emails, enabled by the new option `implicit_tls`. Contributed by Jan Schär.
7 changes: 6 additions & 1 deletion docs/usage/configuration/config_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3150,9 +3150,13 @@ Server admins can configure custom templates for email content. See

This setting has the following sub-options:
* `smtp_host`: The hostname of the outgoing SMTP server to use. Defaults to 'localhost'.
* `smtp_port`: The port on the mail server for outgoing SMTP. Defaults to 25.
* `smtp_port`: The port on the mail server for outgoing SMTP. Defaults to 465 if `implicit_tls` is true, else 25.
jscissr marked this conversation as resolved.
Show resolved Hide resolved
* `smtp_user` and `smtp_pass`: Username/password for authentication to the SMTP server. By default, no
authentication is attempted.
* `implicit_tls`: By default, Synapse connects over plain text and then optionally upgrades
to TLS via STARTTLS. If this option is set to true, TLS is used from the start,
and the options `require_transport_security` and `enable_tls` are ignored.
It is recommended to enable this if supported by your mail server.
jscissr marked this conversation as resolved.
Show resolved Hide resolved
* `require_transport_security`: Set to true to require TLS transport security for SMTP.
By default, Synapse will connect over plain text, and will then switch to
TLS via STARTTLS *if the SMTP server supports it*. If this option is set,
Expand Down Expand Up @@ -3217,6 +3221,7 @@ email:
smtp_port: 587
smtp_user: "exampleusername"
smtp_pass: "examplepassword"
implicit_tls: true
require_transport_security: true
enable_tls: false
notif_from: "Your Friendly %(app)s homeserver <noreply@example.com>"
Expand Down
5 changes: 4 additions & 1 deletion synapse/config/emailconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,11 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
if email_config is None:
email_config = {}

self.implicit_tls = email_config.get("implicit_tls", False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what happens if implicit_tls is true and enable_tls is false?

We should catch such nonsensical configurations rather than leaving the user to discover their error on their own.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a check for this in 862bc52.

self.email_smtp_host = email_config.get("smtp_host", "localhost")
self.email_smtp_port = email_config.get("smtp_port", 25)
self.email_smtp_port = email_config.get(
"smtp_port", 465 if self.implicit_tls else 25
)
self.email_smtp_user = email_config.get("smtp_user", None)
self.email_smtp_pass = email_config.get("smtp_pass", None)
self.require_transport_security = email_config.get(
Expand Down
36 changes: 26 additions & 10 deletions synapse/handlers/send_email.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@

import twisted
from twisted.internet.defer import Deferred
from twisted.internet.interfaces import IOpenSSLContextFactory, IReactorTCP
from twisted.internet.interfaces import IOpenSSLContextFactory
from twisted.internet.ssl import optionsForClientTLS
from twisted.mail.smtp import ESMTPSender, ESMTPSenderFactory

from synapse.logging.context import make_deferred_yieldable
from synapse.types import ISynapseReactor

if TYPE_CHECKING:
from synapse.server import HomeServer
Expand All @@ -48,7 +50,7 @@ def _getContextFactory(self) -> Optional[IOpenSSLContextFactory]:


async def _sendmail(
reactor: IReactorTCP,
reactor: ISynapseReactor,
smtphost: str,
smtpport: int,
from_addr: str,
Expand All @@ -59,6 +61,7 @@ async def _sendmail(
require_auth: bool = False,
require_tls: bool = False,
enable_tls: bool = True,
implicit_tls: bool = False,
) -> None:
"""A simple wrapper around ESMTPSenderFactory, to allow substitution in tests

Expand All @@ -73,8 +76,9 @@ async def _sendmail(
password: password to give when authenticating
require_auth: if auth is not offered, fail the request
require_tls: if TLS is not offered, fail the reqest
enable_tls: True to enable TLS. If this is False and require_tls is True,
enable_tls: True to enable STARTTLS. If this is False and require_tls is True,
the request will fail.
implicit_tls: True to enable Implicit TLS.
"""
msg = BytesIO(msg_bytes)
d: "Deferred[object]" = Deferred()
Expand Down Expand Up @@ -105,13 +109,23 @@ def build_sender_factory(**kwargs: Any) -> ESMTPSenderFactory:
# set to enable TLS.
factory = build_sender_factory(hostname=smtphost if enable_tls else None)

reactor.connectTCP(
smtphost,
smtpport,
factory,
timeout=30,
bindAddress=None,
)
if implicit_tls:
reactor.connectSSL(
smtphost,
smtpport,
factory,
optionsForClientTLS(smtphost),
timeout=30,
bindAddress=None,
)
else:
reactor.connectTCP(
smtphost,
smtpport,
factory,
timeout=30,
bindAddress=None,
)

await make_deferred_yieldable(d)

Expand All @@ -132,6 +146,7 @@ def __init__(self, hs: "HomeServer"):
self._smtp_pass = passwd.encode("utf-8") if passwd is not None else None
self._require_transport_security = hs.config.email.require_transport_security
self._enable_tls = hs.config.email.enable_smtp_tls
self._implicit_tls = hs.config.email.implicit_tls

self._sendmail = _sendmail

Expand Down Expand Up @@ -189,4 +204,5 @@ async def send_email(
require_auth=self._smtp_user is not None,
require_tls=self._require_transport_security,
enable_tls=self._enable_tls,
implicit_tls=self._implicit_tls,
)