]> git.ipfire.org Git - dbl.git/commitdiff
users: Deliver emails over SMTP instead of piping them into sendmail
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 17 Feb 2026 16:14:41 +0000 (16:14 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 17 Feb 2026 16:14:41 +0000 (16:14 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/dbl/__init__.py
src/dbl/users.py
src/dbl/util.py

index 40ad9ede7a03e73ef41662e8879e62d6be8ccab1..eaf374ac62b6cf41871e4e7ce418f2da54e5d9f8 100644 (file)
@@ -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
index 9da85e732ff7771625b671438c24cf0e971472b3..5ea7f827b90fa241eb763a047a9666bc753fa58f 100644 (file)
@@ -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):
index 8f2b082a90eee401d31a57c2b2411c221d0be520..9bb60493d91256daa23a093bf960a405e4140e6b 100644 (file)
@@ -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("@", "=")