address commands. See manual page for more information.
"""
-VERSION = "1.2.1"
+VERSION = "1.2.6"
PLUGIN_INTERFACE_VERSION = "1"
import getopt
-import md5
+import hashlib
import os
import shutil
import smtplib
def md5sum_as_hex(s):
- return md5.new(s).hexdigest()
+ return hashlib.md5(s).hexdigest()
+
+def forkexec(argv, text):
+ """Run a command (given as argv array) and write text to its stdin"""
+ (r, w) = os.pipe()
+ pid = os.fork()
+ if pid == -1:
+ raise Exception("fork failed")
+ elif pid == 0:
+ os.dup2(r, 0)
+ os.close(r)
+ os.close(w)
+ fd = os.open("/dev/null", os.O_RDWR)
+ os.dup2(fd, 1)
+ os.dup2(fd, 2)
+ os.execvp(argv[0], argv)
+ sys.exit(1)
+ else:
+ os.close(r)
+ os.write(w, text)
+ os.close(w)
+ (pid2, exit) = os.waitpid(pid, 0)
+ if pid != pid2:
+ raise Exception("os.waitpid for %d returned for %d" % (pid, pid2))
+ if exit != 0:
+ raise Exception("subprocess failed, exit=0x%x" % exit)
+ return exit
+
environ = None
"\n ".join(text[:text.find("\n\n")].split("\n"))))
if recipients:
if self.smtp_server:
- smtp = smtplib.SMTP(self.smtp_server)
- smtp.sendmail(envelope_sender, recipients, text)
- smtp.quit()
+ try:
+ smtp = smtplib.SMTP(self.smtp_server)
+ smtp.sendmail(envelope_sender, recipients, text)
+ smtp.quit()
+ except:
+ error("Error sending SMTP mail, mail probably not sent")
+ sys.exit(1)
elif self.qmqp_server:
- q = qmqp.QMQP(self.qmqp_server)
- q.sendmail(envelope_sender, recipients, text)
- q.quit()
+ try:
+ q = qmqp.QMQP(self.qmqp_server)
+ q.sendmail(envelope_sender, recipients, text)
+ q.quit()
+ except:
+ error("Error sending QMQP mail, mail probably not sent")
+ sys.exit(1)
else:
- recipients = string.join(recipients, " ")
- f = os.popen("%s -oi -f '%s' %s" %
- (self.sendmail,
- envelope_sender,
- recipients),
- "w")
- f.write(text)
- f.close()
+ status = forkexec([self.sendmail, "-oi", "-f",
+ envelope_sender] + recipients, text)
+ if status:
+ error("%s returned %s, mail sending probably failed" %
+ (self.sendmail, status))
+ sys.exit((status >> 8) & 0xff)
else:
debug("send_mail: no recipients, not sending")
self.cp.set("list", "ignore-bounce", "no")
self.cp.set("list", "language", "")
self.cp.set("list", "pristine-headers", "")
+ self.cp.set("list", "subject-prefix", "")
self.dirname = os.path.join(self.mlm.dotdir, name)
self.make_listdir()
def read_stdin(self):
data = sys.stdin.read()
+ # Convert CRLF to plain LF
+ data = "\n".join(data.split("\r\n"))
# Skip Unix mbox "From " mail start indicator
if data[:5] == "From ":
data = string.split(data, "\n", 1)[1]
return True
def mime_encode_headers(self, text):
- headers, body = text.split("\n\n", 1)
-
- list = []
- for line in headers.split("\n"):
- if line[0].isspace():
- list[-1] += line
- else:
- list.append(line)
-
- headers = []
- for header in list:
- if self.nice_7bit(header):
- headers.append(header)
- else:
- if ": " in header:
- name, content = header.split(": ", 1)
+ try:
+ headers, body = text.split("\n\n", 1)
+
+ list = []
+ for line in headers.split("\n"):
+ if line[0].isspace():
+ list[-1] += line
else:
- name, content = header.split(":", 1)
- hdr = email.Header.Header(content, "utf-8")
- headers.append(name + ": " + hdr.encode())
-
- return "\n".join(headers) + "\n\n" + body
+ list.append(line)
+
+ headers = []
+ for header in list:
+ if self.nice_7bit(header):
+ headers.append(header)
+ else:
+ if ": " in header:
+ name, content = header.split(": ", 1)
+ else:
+ name, content = header.split(":", 1)
+ hdr = email.Header.Header(content, "utf-8")
+ headers.append(name + ": " + hdr.encode())
+
+ return "\n".join(headers) + "\n\n" + body
+ except:
+ info("Cannot MIME encode header, using original ones, sorry")
+ return text
def template(self, template_name, dict):
lang = self.cp.get("list", "language")
return
if self.cp.get("list", "pristine-headers") != "yes":
text = self.mime_encode_headers(text)
+ text = self.add_subject_prefix(text)
self.mlm.send_mail(envelope_sender, recipients, text)
def send_info_message(self, recipients, template_name, dict):
"boundary": self.invent_boundary(),
})
else:
- self.info_message([recipient], "setlist-sorry", {})
+ self.send_info_message([recipient], "setlist-sorry", {})
def parse_setlist_addresses(self, text):
body = text.split("\n\n", 1)[1]
def obey_owner(self, text):
sender = get_from_environ("SENDER")
recipients = self.cp.get("list", "owners").split()
+ text = self.add_subject_prefix(text)
self.mlm.send_mail(sender, recipients, text)
def obey_subscribe_or_unsubscribe(self, dict, template_name, command,
else:
return ""
- def remove_some_headers(self, mail, headers_to_remove):
+ def headers_and_body(self, mail):
endpos = mail.find("\n\n")
if endpos == -1:
endpos = mail.find("\n\r\n")
return mail
headers = mail[:endpos].split("\n")
body = mail[endpos:]
+ return (headers, body)
+
+ def remove_some_headers(self, mail, headers_to_remove):
+ headers, body = self.headers_and_body(mail)
+
+ headers_to_remove = [x.lower() for x in headers_to_remove]
remaining = []
add_continuation_lines = 0
+
for header in headers:
- pos = header.find(":")
- if pos == -1:
+ if header[0] in [' ','\t']:
+ # this is a continuation line
if add_continuation_lines:
remaining.append(header)
else:
+ pos = header.find(":")
+ if pos == -1:
+ # malformed message, try to remove the junk
+ add_continuation_lines = 0
+ continue
name = header[:pos].lower()
if name in headers_to_remove:
add_continuation_lines = 0
return text + self.template("footer", {})
def send_mail_to_subscribers(self, text):
+ text = self.remove_some_headers(text, ["list-id", "list-help",
+ "list-unsubscribe",
+ "list-subscribe", "list-post",
+ "list-owner", "precedence"])
text = self.headers_to_add() + self.list_headers() + \
self.headers_to_remove(text)
+ text = self.add_subject_prefix(text)
text = self.append_footer(text)
text, = self.mlm.call_plugins("send_mail_to_subscribers_hook",
self, text)
addresses = self.subscribers.in_group(group)
self.mlm.send_mail(bounce, addresses, text)
+ def add_subject_prefix(self, text):
+ """Given a full-text mail, munge its subject header with the configured
+ subject prefix (if any) and return the updated mail text.
+ """
+ headers, body = self.headers_and_body(text)
+
+ prefix = self.cp.get("list", "subject-prefix")
+
+ # We don't need to do anything special to deal with multi-line
+ # subjects since adding the prefix to the first line of the subject
+ # and leaving the later lines untouched is sufficient.
+ if prefix:
+ has_subject = False
+ for header in headers:
+ if header.startswith('Subject:'):
+ has_subject = True
+ if prefix not in header:
+ text = text.replace(header,
+ header[:9] + prefix + " " + header[9:], 1)
+ break
+ # deal with the case where there was no Subject in the original
+ # mail (broken mailer?)
+ if not has_subject:
+ text = text.replace("\n\n", "Subject: " + prefix + "\n\n", 1)
+
+ return text
+
def post_into_moderate(self, poster, dict, text):
id = self.moderation_box.add(poster, text)
recipients = self.moderators()