commit e05366916db075f9e35be123c9c6fda7300de94c Author: François Poulain Date: Mon Aug 24 18:07:13 2020 +0200 init diff --git a/mailing.py b/mailing.py new file mode 100755 index 0000000..b590930 --- /dev/null +++ b/mailing.py @@ -0,0 +1,226 @@ +#! /usr/bin/env python3 + +# (C) Olivier Berger 2001-2002 +# (C) François Poulain 2019 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA +# 02111-1307, USA. +# + +# This program is used to mail a message to several recipients, thus +# providing customization for each recipient. + +import sys, getopt, re +import smtplib, time, mimetypes +from email.header import Header +from email.message import EmailMessage + +# +# displays usage of the program, then quit +# +def usage(returncode): + print("""SYNTAX: + """, sys.argv[0], """ -t tofile -b bodyfile [-p attachedfile] + + tofile : sort of CSV file containing addresses of recipients + bodyfile : template of the mail to be sent + attachedfile : optionnal attachment + """) + sys.exit(returncode) + + +TOFILE = None +BODYFILE = None +JOINFILE = None + + +# read the recipients file where values are separated by | characters +def read_recipients() : + recipientfile = open(TOFILE) + lines = recipientfile.readlines() + return [line[:-1].split('|') for line in lines] + + +def read_body() : + bodyfile = open(BODYFILE) + body = "" + for line in bodyfile.readlines() : + body = body + line + return body + +def read_join() : + try: + with open(JOINFILE, 'rb') as fp: + ctype, encoding = mimetypes.guess_type(JOINFILE) + if ctype is None or encoding is not None: + ctype = 'application/octet-stream' + maintype, subtype = ctype.split('/', 1) + metadata = { + 'filename': JOINFILE, + 'maintype': maintype, + 'subtype': subtype, + } + return (fp.read(), metadata) + except Exception as e: + print("read error: %s" % e) + exit(1) + +def replace_values(bodytemplate, values) : + body = bodytemplate + + for i in range(len(values)) : + i = i+1 + pattern = "${{%02d}}" % i + + body = body.replace(pattern, values[i-1]) + + return body + + +# patterns for header of the mail +FROMPATTERN = re.compile(r"^From: *(.*)") +SUBJECTPATTERN = re.compile(r"^Subject: *(.*)") +CCPATTERN = re.compile(r"^Cc: *(.*)") +BCCPATTERN = re.compile(r"^Bcc: *(.*)") +REPLYTOPATTERN = re.compile(r"^Reply-To: *(.*)") + +def qencode_subject (message): + subjectmatches = re.search (r"^Subject: *(.*)$", message, re.MULTILINE) + if (subjectmatches == None): return message + subjectstr = subjectmatches.group(1) + h = Header(subjectstr, 'utf-8') + qsubjectstr=h.encode() + return message.replace (subjectstr, qsubjectstr, 1) + +def send_message(message, to, attachment) : + + lines = message.split('\n') + + # identify headers in the template + fromvalue = None + subjectvalue = None + ccvalue = None + bccvalue = None + replytovalue = None + + for index, line in enumerate(lines) : + if not line : + body = '\n'.join(lines[index+1:]) + break + + frommatches = FROMPATTERN.match(line) + subjectmatches = SUBJECTPATTERN.match(line) + ccmatches = CCPATTERN.match(line) + bccmatches = BCCPATTERN.match(line) + replytomatches = REPLYTOPATTERN.match(line) + + if frommatches : + fromvalue = frommatches.group(1) + if subjectmatches : + subjectvalue = subjectmatches.group(1) + if ccmatches : + ccvalue = ccmatches.group(1) + if bccmatches : + bccvalue = bccmatches.group(1) + if replytomatches : + replytovalue = replytomatches.group(1) + + # lists all addresses to which the mail will be sent + dests = [to] + + if ccvalue : + dests.append(ccvalue) + if bccvalue : + dests.append(bccvalue) + + msg = EmailMessage() + msg['Subject'] = subjectvalue + msg['From'] = fromvalue + msg['To'] = to + if replytovalue: + msg['Reply-To'] = replytovalue + if ccvalue : + msg['Cc'] = ccvalue + msg.set_content(body) + if attachment: + msg.add_attachment(attachment[0], **attachment[1]) + + print("Sending : %s, from %s to %s, dests : %s" % (subjectvalue, fromvalue, dests[0], repr(dests))) + + # sending the mail to its recipients using the local mailer via SMTP + server = smtplib.SMTP('localhost','25') + server.set_debuglevel(1) + server.send_message(msg) + server.quit() + + + + +if __name__ == '__main__' : + + + # handle options of the program + try: + opts, args = getopt.getopt(sys.argv[1:], 't:b:p:') + except getopt.error as msg: + print("getopt error: %s" % msg) + usage(1) + + for name, value in opts: + if name == '-t': TOFILE = value + elif name == '-b': BODYFILE = value + elif name == '-p': JOINFILE = value + else: + print("argument: ", name, " invalid") + usage(1) + + if not TOFILE or not BODYFILE: + usage(1) + + # read the recipients file + sets = read_recipients() + + # read the template of the mail + bodytemplate = read_body() + + # optionnaly read the attachment of the mail + attachment = None + if JOINFILE: + attachment = read_join() + + # counter to be able to pause each 10 mails send + counter = 0 + + # send mail to each recipient + for set in sets : + + values = set + + # the recipient is always in first column + recipient = values[0] + + # replace substitution variables in the template for each recipient + body = replace_values(bodytemplate, values) + + # send message + send_message(body, recipient, attachment) + + # pause every 10 mails for 5 secs so that the MTA doesn't explode + counter = counter + 1 + if counter >= 10 : + counter = 0 + print("suspending execution for 5 secs") + time.sleep(5) +