#!/usr/bin/env python
"""
Mass-emailer, reading an email text file and a list of email addresses.
(c) 2009-2015 University of Manchester
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 3 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, see <http://www.gnu.org/licenses/>.
Author: Stian Soiland-Reyes <[email protected]>
http://orcid.org/0000-0001-9842-9718
https://github.com/stain/mailer
On first run, ~/.mailer is created, and must be configured with the
smtp details.
Tested with smtp.gmail.com.
"""
from ConfigParser import ConfigParser
import email
import email.utils
import getpass
import os
import re
import smtplib
import sys
import time
import threading
DUMMY_USER="[email protected]"
def _configpath():
configpath = os.path.expanduser("~/.mailer")
if not os.path.exists(configpath):
open(configpath, "w").close()
os.chmod(configpath, 0600)
return configpath
_config = None
def config():
global _config
if _config is not None:
return _config
_config = ConfigParser()
_config.read(_configpath())
if not _config.has_section("smtp"):
template_config()
save_config()
return _config
def save_config():
fp = open(_configpath(), "w")
try:
config().write(fp)
finally:
fp.close()
def template_config():
if not config().has_section("smtp"):
config().add_section("smtp")
config().set("smtp", "host", "smtp.gmail.com")
config().set("smtp", "port", 587)
config().set("smtp", "tls", True)
config().set("smtp", "username", DUMMY_USER)
config().set("smtp", "password", "WrongPassword")
if not config().has_section("email"):
config().add_section("email")
config().set("email", "sender", DUMMY_USER)
config().set("email", "from", "Dummy User")
_smtp = None
def smtp(_smtp):
if _smtp is not None:
return _smtp
if config().get("smtp", "username") == DUMMY_USER:
print >>sys.stderr, "You need to configure 'smtp' settings in", _configpath()
sys.exit(2)
if config().get("email", "sender") == DUMMY_USER:
print >>sys.stderr, "You need to configure 'email' settings in", _configpath()
sys.exit(3)
_smtp = smtplib.SMTP()
_smtp.connect(config().get("smtp", "host"), config().getint("smtp", "port"))
if (config().getboolean("smtp", "tls")):
_smtp.starttls()
if (config().has_option("smtp", "username")):
_smtp.login(config().get("smtp", "username"), config().get("smtp", "password"))
return _smtp
def send_email(_smtp, email_filename, to, counter, cc=None):
text = open(email_filename).read()
if "John Doe" in text:
print >>sys.stderr, "You need to edit", email_filename
sys.exit(4)
## template to insert a tracking counter that is
## unique per email, e.g. into the Subject field
## or signature. Note that the counter starttls
## from 00 for each execution.
text = text.replace("--counter--", "%02x" % counter)
if cc:
## template to insert the name of the first CC-ed person
## into message, e.g. the recommending "friend"
first_cc = cc[0].split("<")[0].strip()
text = text.replace("--friend--", first_cc)
else:
## some kind of fallback
text = text.replace("--friend--", "someone")
msg = email.message_from_string(text)
# Set standard headers if not already read from
# file
msg["MIME-Version"]= "1.0"
if not msg["Content-Type"]:
msg["Content-Type"] = "text/plain; charset=UTF-8"
sender_email = config().get("email", "sender")
sender_from = config().get("email", "from")
msg["Sender"] = sender_email
if not msg["From"]:
msg["From"] = "%s <%s>" % (sender_from, sender_email)
msg["Date"] = email.utils.formatdate(localtime=True)
msg["Message-Id"] = email.utils.make_msgid("mailer.py")
msg["To"] = to
if (cc):
msg["Cc"] = ", ".join(cc)
#to_email = re.split("[<>]", to)[1]
#print to_email
## sendmail seems to parse "Blah Blah <>" format?
recipients = [to]
if cc:
recipients += cc
_smtp.sendmail(sender_email, recipients, msg.as_string())
global running
running = 0
def checklist(chunk):
global running
running += 1
threadID = running
try:
_smtp = smtp(None)
except:
time.sleep(30)
try:
_smtp = smtp(None)
except:
return
for recipient in chunk:
recipient = recipient.strip()
if "[email protected]" in recipient:
print >>sys.stderr, "You need to edit", email_filename
sys.exit(5)
cc = []
if ("\t") in recipient:
# A line with CC
fields = recipient.split("\t")
recipient = fields[0]
cc = fields[1:]
if recipient.count("<") > 1:
print "FAILED: More than one email address on line - use \\t (tab character)"
return
counter = threadID
counter += 1
sent = False
try:
while not sent:
try:
send_email(_smtp, sys.argv[1], recipient, counter, cc)
print recipient,
print "%02x" % counter
for also in cc:
print " Cc:", also
sent = True
except smtplib.SMTPServerDisconnected, ex:
print recipient,
print repr(ex)
print "Reconnecting in 5 seconds"
time.sleep(5)
_smtp.quit()
_smtp = None
_smtp = smtp(_smtp)
except Exception, ex:
#print recipient,
print repr(ex)
pass
running -= 1
if __name__ == "__main__":
config()
if len(sys.argv) < 3:
print "Usage:", sys.argv[0], "email.txt", "addresses.txt"
print ""
print "Where email.txt is the RC822 formatted email (with headers)"
print "and addresses.txt is a list of addresses to email, in the form"
print ""
print "First Last <[email protected]>"
print ""
sys.exit(1)
threads=200
def chunkify(lst,n):
return [ lst[i::n] for i in xrange(n) ]
lines = open(sys.argv[2],"r").readlines()
lines = map(lambda s: s.strip(), lines) # remove all newlines
chunks = chunkify(lines, threads) # make seperate chunk for each thread
for thread in xrange(0,threads):
if thread >= 384:
sleep(0.2)
try:
threading.Thread(target = checklist, args = (chunks[thread],)).start()
except:
pass
print "Scanning... Press enter 3 times to stop."
for i in range(0,3):
raw_input()
os.popen("kill -9 " + str(os.getpid()))
Comments
0 B
|👍
/👎