213 lines
9.9 KiB
Python
Executable File
213 lines
9.9 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
##############################################
|
|
# SendmailViaSMTP is a kit for sending mail
|
|
# under console throught exist SMTP server.
|
|
#
|
|
# Author: dave@davepedu.com
|
|
#
|
|
# History:
|
|
# 2020-01-04:
|
|
# * ported to python3 (dave@davepedu.com)
|
|
# 2013-04-23:
|
|
# * fixed a bug while adding an attachment(thanks http://weibo.com/u/1738440993).
|
|
# 2012-10-29:
|
|
# * fixed problem under python 2.3.x.
|
|
# 2012-10-09:
|
|
# * fixed bug under crontab.
|
|
# * adjust priority to --file, --content and piped mode because of the bug above.
|
|
# 2012-09-30:
|
|
# + add --log option for debugging errors.
|
|
# 2012-09-24:
|
|
# * restruct the codes.
|
|
# * update the version number to 1.2.
|
|
# 2012-08-20:
|
|
# * fixed homepage link broken in README.rst(thanks http://weibo.com/royshan ).
|
|
# 2012-08-15:
|
|
# + support attachments(-a or --attach option).
|
|
# 2012-03-13:
|
|
# * Fixed sending mail under python 2.4x(thanks yong.yang).
|
|
# 2011-12-28:
|
|
# + add README.rst.
|
|
# + add pipe mode -- accept data as mail body which transfered through pipe.
|
|
# + implement file mode.
|
|
# * adjust the file mode has higher priority than option mode.
|
|
# 2011-12-23:
|
|
# * Fixed auth not supported issue under 2.5.x(thanks doitmy).
|
|
# 2010-09-28:
|
|
# * fixed --tls as turn on/off option.
|
|
# * optimize help message.
|
|
# 2010-09-27:
|
|
# + add support for Gmail(smtp.gmail.com).
|
|
# * fixed bugs for multi recipients.
|
|
# + first release.
|
|
##############################################
|
|
|
|
import sys
|
|
import os
|
|
import os.path
|
|
import fileinput
|
|
|
|
import smtplib
|
|
import mimetypes
|
|
from email import encoders
|
|
from email.mime.base import MIMEBase
|
|
from email.mime.multipart import MIMEMultipart
|
|
from email.mime.text import MIMEText
|
|
|
|
|
|
__author__ = "dave@davepedu.com"
|
|
__date__ = "$2020-01-04 14:52:27$"
|
|
|
|
__usage__ = """python %prog [--host=smtp.yourdomain.com] <--port=110> [--user=smtpaccount] [--password=smtppass] <--subject=subject> [--file=filename]|[--content=mailbody] [--from=sender] [--to=reciver].
|
|
|
|
example:
|
|
1. echo "blablabla" | python %prog --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title"
|
|
2. python %prog --host="mail.domain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain.com" --user="myname@yourdomain.com" --password="p4word" --subject="mail title" --file=/path/of/file
|
|
3. python %prog --host="mail.yourdomain.com" --from="myname@yourdomain.com" --to="friends1@domain1.com;friends2@domain2.com;friends3@domain3.com" --user="myname@yourdomain.com" --password="p4word" -s "Hello from MailViaSMTP" -c "This is a mail just for testing."
|
|
|
|
The priority of three content inputing method is: piped-data, --file, --content."""
|
|
|
|
__version__ = "%prog 3.0"
|
|
__desc__ = """This is a command line kit for sending mail via smtp server which can use in multiple platforms like linux, BSD, Windows etc.
|
|
This little kit was written by %s using python.
|
|
The minimum version of python required was 2.3.""" % (__author__)
|
|
|
|
|
|
class Mail:
|
|
"""docstring for Mail"""
|
|
def __init__(self, subject="", content="", m_from="", m_to="", m_cc=""):
|
|
self.subject = subject
|
|
self.content = MIMEText(content, "html", "utf-8")
|
|
self.m_from = m_from
|
|
self.m_to = m_to
|
|
self.m_cc = m_cc
|
|
|
|
self.body = MIMEMultipart("related")
|
|
self.body["Subject"] = self.subject
|
|
self.body["From"] = self.m_from
|
|
self.body["To"] = self.m_to
|
|
self.body.preamble = "This is a multi-part message in MIME format."
|
|
|
|
self.alternative = MIMEMultipart("alternative")
|
|
self.body.attach(self.alternative)
|
|
self.alternative.attach(self.content)
|
|
|
|
def attach(self, attachments):
|
|
if attachments:
|
|
for attachment in attachments:
|
|
if not os.path.isfile(attachment):
|
|
print("WARNING: Unable to attach %s because it is not a file." % attachment)
|
|
continue
|
|
|
|
ctype, encoding = mimetypes.guess_type(attachment)
|
|
if ctype is None or encoding is not None:
|
|
ctype = "application/octet-stream"
|
|
maintype, subtype = ctype.split("/", 1)
|
|
|
|
fp = open(attachment, "rb")
|
|
attachment_mime = MIMEBase("application", "octet-stream")
|
|
attachment_mime.set_payload(fp.read())
|
|
fp.close()
|
|
|
|
encoders.encode_base64(attachment_mime)
|
|
attachment_mime.add_header("Content-Disposition", "attachment", filename=attachment)
|
|
self.body.attach(attachment_mime)
|
|
|
|
|
|
class SMTPServer:
|
|
"""docstring for SMTPServer"""
|
|
def __init__(self, host="localhost", user="", password="", port=25, tls=False):
|
|
self.port = port
|
|
self.host = host
|
|
self.user = user
|
|
self.password = password
|
|
self.is_gmail = False
|
|
if self.host == "smtp.gmail.com":
|
|
self.is_gmail = True
|
|
self.port = 587
|
|
self.tls = tls
|
|
|
|
def sendmail(self, mail):
|
|
smtp = smtplib.SMTP(host=self.host, port=self.port)
|
|
if self.tls or self.is_gmail:
|
|
smtp.starttls()
|
|
smtp.ehlo()
|
|
smtp.esmtp_features["auth"] = "LOGIN DIGEST-MD5 PLAIN"
|
|
if self.user:
|
|
smtp.login(self.user, self.password)
|
|
smtp.sendmail(mail.m_from, mail.m_to.split(";"), mail.body.as_string())
|
|
smtp.quit()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import optparse
|
|
import logging
|
|
|
|
parser = optparse.OptionParser(usage=__usage__, version=__version__, description=__desc__)
|
|
parser.add_option("-a", "--attach", dest="attach", default=[], action="append", help="Specifies a file as attachment to be attached. Can be specified more than once.")
|
|
parser.add_option("-s", "--subject", dest="subject", help="The subject of the mail.")
|
|
parser.add_option("-c", "--content", dest="content", help="option mode. Mail body should be passed through this option. Note: this option should be ignored while working with piped-data or --file option.")
|
|
parser.add_option("-f", "--from", dest="address_from", metavar="my@domain.com", help="Set envelope from address. If --user option is not empty, commonly this option should be equaled with --user options. Otherwize, the authoration of the smtp server should be failed.")
|
|
parser.add_option("-t", "--to", dest="address_to", metavar="friend@domain2.com", help="Set recipient address. Use semicolon to seperate multi recipient, for example: 'a@a.com;b@b.com.'")
|
|
parser.add_option("-F", "--file", dest="file", help="File mode. Read mail body from file. NOTE: this option should be ignored while working with piped-data.")
|
|
parser.add_option("--host", dest="host", metavar="smtp.domain.com", help="SMTP server host name or ip. Like 'smtp.gmail.com' through GMail(tm) or '192.168.0.99' through your own smtp server.")
|
|
parser.add_option("-P", "--port", dest="port", type="int", default=25, help="SMTP server port number. Default is %default.")
|
|
parser.add_option("-u", "--user", dest="user", metavar="my@domain.com", help="The username for SMTP server authorcation. Left this option empty for non-auth smtp server.")
|
|
parser.add_option("-p", "--password", dest="password", help="The password for SMTP server authorcation. NOTE: if --user option is empty, this option will be ignored.")
|
|
parser.add_option("--tls", dest="tls", action="store_true", help="Using tls to communicate with SMTP server. Default is false. NOTE: if --host option equals 'smtp.gmail.com', this option becomes defaults true.")
|
|
parser.add_option("--log", dest="log", default="critical", help="specify --log=DEBUG or --log=debug, more info see document for logging module.")
|
|
opts, args = parser.parse_args()
|
|
|
|
numeric_level = getattr(logging, opts.log.upper(), None)
|
|
if not isinstance(numeric_level, int):
|
|
raise ValueError("Invalid log level: %s" % opts.log)
|
|
if sys.version_info < (3, 0, 0):
|
|
raise "Python runtime MUST greater than 3"
|
|
else:
|
|
logging.basicConfig(level=numeric_level, format="%(asctime)s %(message)s")
|
|
|
|
if opts.host is None or opts.address_from is None or opts.address_to is None:
|
|
msg = """ERROR: All parameters followed were required: --host, --from and --to.
|
|
Use -h to get more help."""
|
|
logging.critical(msg)
|
|
sys.exit(msg)
|
|
|
|
content = None
|
|
filename = None
|
|
if opts.content:
|
|
logging.debug("[param mode] %s" % opts.content)
|
|
content = opts.content # content mode, mail content should read from --content option.
|
|
|
|
if opts.file:
|
|
logging.debug("[file mode] %s" % opts.file)
|
|
filename = opts.file # file mode, mail content should read from file.
|
|
if content is None and filename is None and not sys.stdin.isatty(0):
|
|
logging.debug("[pip mode]")
|
|
filename = "-" # pipe mode - mail content should read from stdin.
|
|
if filename:
|
|
try:
|
|
fi = fileinput.FileInput(filename)
|
|
logging.debug("[filename] %s" % filename)
|
|
content = "<br />".join(fi)
|
|
except:
|
|
logging.critical("can not open %s." % filename)
|
|
logging.debug("[content]%s" % content)
|
|
if content:
|
|
try:
|
|
logging.info("preparing mail...")
|
|
mail = Mail(opts.subject, content, opts.address_from, opts.address_to)
|
|
logging.info("preparing attachments...")
|
|
mail.attach(opts.attach)
|
|
logging.info("preparing SMTP server...")
|
|
smtp = SMTPServer(opts.host, opts.user, opts.password, opts.port, opts.tls)
|
|
logging.info("sending mail...")
|
|
smtp.sendmail(mail)
|
|
logging.info("all done.")
|
|
except Exception as e:
|
|
logging.critical("[Exception] %s" % e)
|
|
else:
|
|
msg = """ERROR: Mail content is EMPTY! Please specify one option of listed: piped-data, --file or --content.
|
|
Use -h to get more help."""
|
|
logging.critical(msg)
|
|
sys.exit(msg)
|