From: Michael Tremer Date: Tue, 17 Feb 2026 16:14:41 +0000 (+0000) Subject: users: Deliver emails over SMTP instead of piping them into sendmail X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=49af38e66df9f09c11a6fc1a23fc4718a65293a4;p=dbl.git users: Deliver emails over SMTP instead of piping them into sendmail Signed-off-by: Michael Tremer --- diff --git a/src/dbl/__init__.py b/src/dbl/__init__.py index 40ad9ed..eaf374a 100644 --- a/src/dbl/__init__.py +++ b/src/dbl/__init__.py @@ -24,7 +24,10 @@ import httpx import io import logging import publicsuffix2 +import smtplib +import socket import sqlmodel +import ssl # Initialize logging as early as possible from . import logger @@ -138,6 +141,42 @@ class Backend(object): def users(self): return users.Users(self) + @functools.cached_property + def ssl_context(self): + # Create SSL context + context = ssl.create_default_context() + + # Fetch the paths to the certificate & key + cert = self.config.get("ssl", "cert", fallback=None) + key = self.config.get("ssl", "key", fallback=None) + + log.debug("Initializing SSL context") + + # Load the certificate and key if they are set + if cert and key: + log.debug(" Certificate: %s" % cert) + log.debug(" Key: %s" % key) + + # Load the credentials + context.load_cert_chain(cert, key) + + return context + + @property + def relay(self): + """ + Connection to the local mail relay + """ + hostname = socket.getfqdn() + + # Open SMTP connection + conn = smtplib.SMTP(hostname) + + # Start TLS connection + conn.starttls(context=self.ssl_context) + + return conn + def search(self, name): """ Searches for a domain diff --git a/src/dbl/users.py b/src/dbl/users.py index 9da85e7..5ea7f82 100644 --- a/src/dbl/users.py +++ b/src/dbl/users.py @@ -22,7 +22,9 @@ import email.message import email.utils import ldap import logging -import subprocess +import smtplib + +from . import util # Setup logging log = logging.getLogger(__name__) @@ -201,13 +203,19 @@ class User(LDAPObject): # Log the email log.debug("Sending email:\n%s" % message.as_string()) - # Launch sendmail - sendmail = subprocess.Popen( - ["/usr/sbin/sendmail", "-t", "-oi"], stdin=subprocess.PIPE, text=True, - ) + # Make the address for bounce processing + return_path = util.make_verp_address(self.mail) + + # Send the message to the local SMTP relay + try: + self.backend.relay.send_message(message, from_addr=return_path) + + # Catch any exceptions + except smtplib.SMTPException as e: + log.error("Failed to send email: %s" % e) - # Pipe the email into sendmail - sendmail.communicate(message.as_string()) + # Ignore the exception, because there is no way we can recover from this + return class Group(LDAPObject): diff --git a/src/dbl/util.py b/src/dbl/util.py index 8f2b082..9bb6049 100644 --- a/src/dbl/util.py +++ b/src/dbl/util.py @@ -18,6 +18,7 @@ # # ############################################################################### +import email.utils import idna import ipaddress import logging @@ -133,3 +134,12 @@ def is_url(s): Checks if something is a URL """ return "/" in s + +def make_verp_address(recipient): + """ + Creates a VERP address which we will use for reliable bounce processing + """ + # Parse the address + name, recipient = email.utils.parseaddr(recipient) + + return "bounces+%s@ipfire.org" % recipient.replace("@", "=")