From: Michael Tremer Date: Thu, 16 Jan 2020 09:07:59 +0000 (+0000) Subject: messages: Talk to local relay instead of using sendmail X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3258fa6bb8393f4583620b24df49e212481c6042;p=ipfire.org.git messages: Talk to local relay instead of using sendmail Signed-off-by: Michael Tremer --- diff --git a/src/backend/messages.py b/src/backend/messages.py index ec7d2d58..1a89d232 100644 --- a/src/backend/messages.py +++ b/src/backend/messages.py @@ -6,6 +6,9 @@ import email.mime.text import email.utils import logging import random +import smtplib +import socket +import ssl import subprocess import tornado.locale import tornado.template @@ -181,68 +184,80 @@ class Messages(misc.Object): class Queue(misc.Object): + context = ssl.create_default_context() + @property def messages(self): return self.db.query("SELECT * FROM messages \ WHERE time_sent IS NULL \ ORDER BY priority DESC, time_created ASC") + @lazy_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.context) + + return conn + async def send_all(self): # Sends all messages for message in self.messages: - self._sendmail(message) + self.send(message) logging.debug("All messages sent") - def _sendmail(self, message): + def send(self, message): """ - Delivers the given message to sendmail. + Delivers the given message the local mail relay """ - try: - # Parse the message from what is in the database - msg = email.message_from_string(message.message) + # Parse the message from what is in the database + msg = email.message_from_string(message.message) - logging.debug("Sending a message %s to: %s" % ( - msg.get("Subject"), ", ".join(message.envelope_recipients) - )) + logging.debug("Sending a message %s to: %s" % ( + msg.get("Subject"), ", ".join(message.envelope_recipients) + )) - # Make sendmail command line - cmd = [ - "/usr/sbin/sendmail", + error_messages = [] + rejected_recipients = {} - # Don't treat a single line with . as end of input - "-oi", - - # Envelope Sender - "-f", msg.get("From") or "no-reply@ipfire.org", - ] - - # Envelope Recipients - cmd += message.envelope_recipients + # Try delivering the email + try: + rejected_recipients = self.relay.send_message(msg, + to_addrs=message.envelope_recipients) - # Run sendmail and pipe the email in - p = subprocess.Popen(cmd, bufsize=0, close_fds=True, - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + except smtplib.SMTPRecipientsRefused as e: + rejected_recipients = e.recipients - stdout, stderr = p.communicate(message.message.encode("utf-8")) + except smtplib.SMTPException as e: + logging.error("SMTP Exception: %s" % e) + error_messages.append("%s" % e) - # Wait until sendmail has finished - p.wait() + # Log all emails that could not be delivered + for recipient in rejected_recipients: + code, reason = rejected_recipients[recipient] - if p.returncode: - self.db.execute("UPDATE messages SET error_message = %s \ - WHERE id = %s", stdout, message.id) + error_messages.append("Recipient refused: %s - %s (%s)" % \ + (recipient, code, reason.decode())) - logging.error("Could not send mail: %s" % stdout) + if error_messages: + self.db.execute("UPDATE messages SET error_message = %s \ + WHERE id = %s", "; ".join(error_messages), message.id) - # Raise all exceptions - except: - raise + logging.error("Could not send email: %s" % message.id) + for line in error_messages: + logging.error(line) - else: - # After the email has been successfully sent, we mark it as such - self.db.execute("UPDATE messages SET time_sent = NOW() \ - WHERE id = %s", message.id) + # After the email has been successfully sent, we mark it as such + self.db.execute("UPDATE messages SET time_sent = NOW() \ + WHERE id = %s", message.id) def cleanup(self): logging.debug("Cleaning up message queue")