diff options
| author | J08nY | 2017-03-13 02:14:26 +0100 |
|---|---|---|
| committer | J08nY | 2017-07-24 18:44:19 +0200 |
| commit | ad463598885ecc91bee9fbca5aea9fcb4e9f627e (patch) | |
| tree | 9363afa79afaf75c32742c57b6e1cee8754d7a32 /src/mailman/mta/connection.py | |
| parent | 02826321d0430d7ffc1f674eeff4221941689ef7 (diff) | |
| download | mailman-mta-smtps-starttls.tar.gz mailman-mta-smtps-starttls.tar.zst mailman-mta-smtps-starttls.zip | |
Diffstat (limited to 'src/mailman/mta/connection.py')
| -rw-r--r-- | src/mailman/mta/connection.py | 103 |
1 files changed, 92 insertions, 11 deletions
diff --git a/src/mailman/mta/connection.py b/src/mailman/mta/connection.py index 797b979b2..d6824bccf 100644 --- a/src/mailman/mta/connection.py +++ b/src/mailman/mta/connection.py @@ -17,21 +17,23 @@ """MTA connections.""" +import ssl import logging import smtplib from contextlib import suppress from lazr.config import as_boolean from mailman.config import config +from mailman.interfaces.smtp import ISMTPConnection from public import public - +from zope.interface import implementer log = logging.getLogger('mailman.smtp') -@public +@implementer(ISMTPConnection) class Connection: - """Manage a connection to the SMTP server.""" + def __init__(self, host, port, sessions_per_connection, smtp_user=None, smtp_pass=None): """Create a connection manager. @@ -60,24 +62,20 @@ class Connection: self._session_count = None self._connection = None - def _connect(self): - """Open a new connection.""" - self._connection = smtplib.SMTP() - log.debug('Connecting to %s:%s', self._host, self._port) - self._connection.connect(self._host, self._port) + def _login(self): + """Send login if both username and password are specified.""" if self._username is not None and self._password is not None: log.debug('Logging in') self._connection.login(self._username, self._password) - self._session_count = self._sessions_per_connection def sendmail(self, envsender, recipients, msgtext): - """Mimic `smtplib.SMTP.sendmail`.""" if as_boolean(config.devmode.enabled): # Force the recipients to the specified address, but still deliver # to the same number of recipients. recipients = [config.devmode.recipient] * len(recipients) if self._connection is None: self._connect() + self._login() try: log.debug('envsender: %s, recipients: %s, size(msgtext): %s', envsender, recipients, len(msgtext)) @@ -97,9 +95,92 @@ class Connection: return results def quit(self): - """Mimic `smtplib.SMTP.quit`.""" if self._connection is None: return with suppress(smtplib.SMTPException): self._connection.quit() self._connection = None + + +@public +class SMTPConnection(Connection): + """Manage a clear connection to the SMTP server.""" + + def _connect(self): + """Open a new connection.""" + log.debug('Connecting to %s:%s', self._host, self._port) + self._connection = smtplib.SMTP(self._host, self._port) + self._session_count = self._sessions_per_connection + + +class _SSLConnection(Connection): + """Manage a SMTP connection with a SSL context(either SMTPS or STARTTLS)""" + + def __init__(self, host, port, sessions_per_connection, + verify_cert=False, verify_hostname=False, + smtp_user=None, smtp_pass=None): + """ + Create a connection manager with and SSL context. + + :param host: The host name of the SMTP server to connect to. + :type host: string + :param port: The port number of the SMTP server to connect to. + :type port: integer + :param sessions_per_connection: The number of SMTP sessions per + connection to the SMTP server. After this number of sessions + has been reached, the connection is closed and a new one is + opened. Set to zero for an unlimited number of sessions per + connection (i.e. your MTA has no limit). + :type sessions_per_connection: integer + :param verify_cert: Whether to require a server cert and verify it. + Verification in this context means that the server needs to supply + a valid certificate signed by a CA from a set of the system's + default CA certs. + :type verify_cert: bool + :param verify_hostname: Whether to check that the server certificate + specifies the hostname as passed to this constructor. + RFC 2818 and RFC 6125 rules are followed. + :type verify_hostname: bool + :param smtp_user: Optional SMTP authentication user name. If given, + `smtp_pass` must also be given. + :type smtp_user: str + :param smtp_pass: Optional SMTP authentication password. If given, + `smtp_user` must also be given. + """ + super().__init__(host, port, sessions_per_connection, + smtp_user, smtp_pass) + self._tls_context = self._get_context(verify_cert, verify_hostname) + + def _get_context(self, verify_cert, verify_hostname): + """Create and return a new SSLContext.""" + ssl_context = ssl.create_default_context() + ssl_context.check_hostname = verify_hostname + if verify_cert: + ssl_context.verify_mode = ssl.CERT_REQUIRED + else: + ssl_context.verify_mode = ssl.CERT_NONE + return ssl_context + + +@public +class SMTPSConnection(_SSLConnection): + """Manage a SMTPS connection.""" + + def _connect(self): + log.debug('Connecting to %s:%s', self._host, self._port) + self._connection = smtplib.SMTP_SSL(self._host, self._port, + context=self._tls_context) + self._session_count = self._sessions_per_connection + + +@public +class STARTTLSConnection(_SSLConnection, SMTPConnection): + """Manage a plain connection with STARTTLS.""" + + def _connect(self): + super()._connect() + log.debug('Starttls') + try: + self._connection.starttls(context=self._tls_context) + except smtplib.SMTPNotSupportedError as notls: + log.error('Starttls failed: ' + str(notls)) |
