]>
git.ipfire.org Git - ipfire.org.git/blob - src/backend/messages.py
4 import email
.mime
.multipart
10 import tornado
.template
12 from . import accounts
14 from .decorators
import *
16 class Messages(misc
.Object
):
19 return Queue(self
.backend
)
22 def template_loader(self
):
24 Creates a new template loader
26 templates_dir
= self
.backend
.config
.get("global", "templates_dir")
29 return tornado
.template
.Loader(templates_dir
, autoescape
=None)
31 def make_recipient(self
, recipient
):
32 # Use the contact instead of the account
33 if isinstance(recipient
, accounts
.Account
):
34 recipient
= "%s <%s>" % (recipient
, recipient
.email
)
36 # Fall back to pass on strings
40 return email
.utils
.make_msgid("ipfire", domain
="ipfire.org")
43 def bounce_email_address(self
):
44 return self
.settings
.get("bounce_email_address")
46 def _send(self
, recipients
, message
, sender
=None, priority
=0):
48 raise ValueError("Empty list of recipients")
51 recipients
= [self
.make_recipient(r
) for r
in recipients
]
53 res
= self
.db
.get("INSERT INTO messages(message, priority, envelope_recipients) \
54 VALUES(%s, %s, %s) RETURNING id", message
, priority
, recipients
)
56 logging
.debug("Message queued with ID %s" % res
.id)
58 def send(self
, recipients
, message
, priority
=0, headers
={}):
59 # Convert message from string
60 if not isinstance(message
, email
.message
.Message
):
61 message
= email
.message_from_string(message
)
63 # Add a message ID if non exsist
64 if not "Message-Id" in message
and not "Message-ID" in message
:
65 message
.add_header("Message-Id", self
.make_msgid())
70 message
.replace_header(k
, v
)
72 message
.add_header(k
, v
)
74 # Read recipients from To: header
76 recipients
= message
.get("To").split(", ")
78 # Add date if the message doesn't have one already
79 if "Date" not in message
:
80 message
.add_header("Date", email
.utils
.formatdate())
82 # Send any errors to the bounce address
83 if self
.bounce_email_address
:
84 message
.add_header("Errors-To", "<%s>" % self
.bounce_email_address
)
87 self
._send
(recipients
, message
.as_string(), priority
=priority
)
89 def send_template(self
, template_name
, recipients
=[],
90 sender
=None, priority
=0, headers
={}, **kwargs
):
92 Send a message based on the given template
94 locale
= tornado
.locale
.get("en_US")
96 # Create the required namespace to render the message
99 "backend" : self
.backend
,
103 "_" : locale
.translate
,
105 namespace
.update(kwargs
)
107 # Create an alternating multipart message to show HTML or text
108 message
= email
.mime
.multipart
.MIMEMultipart("alternative")
110 for extension
, mimetype
in (("txt", "plain"), ("html", "html")):
112 t
= self
.template_loader
.load("%s.%s" % (template_name
, extension
))
114 # Ignore if the HTML template does not exist
115 if extension
== "html":
118 # Raise all other exceptions
123 message_part
= t
.generate(**namespace
)
125 # Reset the rendered template when it could not be rendered
127 self
.template_loader
.reset()
130 # Parse the message and extract the header
131 message_part
= email
.message_from_string(message_part
.decode())
133 for header
in message_part
:
135 message
.replace_header(header
, message_part
[header
])
137 message
.add_header(header
, message_part
[header
])
139 # Create a MIMEText object out of it
140 message_part
= email
.mime
.text
.MIMEText(
141 message_part
.get_payload(), mimetype
)
143 # Attach the parts to the mime container.
144 # According to RFC2046, the last part of a multipart message
146 message
.attach(message_part
)
149 self
.send(recipients
, message
, priority
=priority
, headers
=headers
)
151 # In debug mode, re-compile the templates with every request
152 if self
.backend
.debug
:
153 self
.template_loader
.reset()
155 async def send_cli(self
, template
, recipient
):
157 Send a test message from the CLI
159 account
= self
.backend
.accounts
.get_by_mail(recipient
)
161 return self
.send_template(template
, recipients
=[recipient
,],
165 class Queue(misc
.Object
):
168 return self
.db
.query("SELECT * FROM messages \
169 WHERE time_sent IS NULL \
170 ORDER BY priority DESC, time_created ASC")
172 async def send_all(self
):
174 for message
in self
.messages
:
175 self
._sendmail
(message
)
177 logging
.debug("All messages sent")
179 def _sendmail(self
, message
):
181 Delivers the given message to sendmail.
184 # Parse the message from what is in the database
185 msg
= email
.message_from_string(message
.message
)
187 logging
.debug("Sending a message %s to: %s" % (
188 msg
.get("Subject"), ", ".join(message
.envelope_recipients
)
191 # Make sendmail command line
193 "/usr/sbin/sendmail",
195 # Don't treat a single line with . as end of input
199 "-f", msg
.get("From") or "no-reply@ipfire.org",
202 # Envelope Recipients
203 cmd
+= message
.envelope_recipients
205 # Run sendmail and pipe the email in
206 p
= subprocess
.Popen(cmd
, bufsize
=0, close_fds
=True,
207 stdin
=subprocess
.PIPE
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.STDOUT
)
209 stdout
, stderr
= p
.communicate(message
.message
.encode("utf-8"))
211 # Wait until sendmail has finished
215 self
.db
.execute("UPDATE messages SET error_message = %s \
216 WHERE id = %s", stdout
, message
.id)
218 logging
.error("Could not send mail: %s" % stdout
)
220 # Raise all exceptions
225 # After the email has been successfully sent, we mark it as such
226 self
.db
.execute("UPDATE messages SET time_sent = NOW() \
227 WHERE id = %s", message
.id)
230 logging
.debug("Cleaning up message queue")
232 self
.db
.execute("DELETE FROM messages \
233 WHERE time_sent IS NOT NULL AND time_sent <= NOW() - '30 day'::interval")