#! /bin/env python
"""
name: sendmail.py
author: Poor Yorick
author_url: http://www.pooryorick.com
license: http://www.ynform.org/w/Pub/OpenSourceSharewareLicense
drop-in substitute for sendmail
"""
import os
import sys
import logging
from optparse import OptionParser, SUPPRESS_HELP
import email
from smtplib import SMTP, SMTP_PORT, SMTP_SSL, SMTP_SSL_PORT
import traceback
logging.basicConfig(level=logging.INFO)
class record(object):
def __init__(self,**kwargs):
for key, val in kwargs.items():
setattr(self, key, val)
def reference(name, opts):
'''return the reference documentation for this script'''
res = '''\
NAME
%(name)s
SYNOPSIS
%(name)s [options] email [email...]
DESCRIPTION
a drop-in sendmail replacement for clients such as mutt
%(reference_keys)s
%(reference_help)s
%(host_keys)s
%(host_help)s
%(login_keys)s
%(login_help)s
%(passfile_keys)s
%(passfile_help)s
for security reasons, this script reads the password from a file to
discourage the use of passwords as shell command arguments
%(security_keys)s
%(security_help)
EXAMPLES
$(name)s -t smtp.<hostname>.com -l <username> -p /path/to/passfile recipient@<somehost>.com < /path/to/email_content
''' % {
'name': name,
'reference_keys': opts.reference.keys,
'reference_help': opts.reference.help,
'host_keys': opts.host.keys,
'host_help': opts.host.help,
'login_keys': opts.login.keys,
'login_help': opts.login.help,
'passfile_keys': opts.passfile.keys,
'passfile_help': opts.passfile.help,
'security_keys': opts.security.keys,
'security_help': opts.security.help,
}
return res
def options(args, myname=None, opts= record(
encrypt = record(keys = ['-e'],
help = 'encrypt communication with MTA. "true" (default), "false", ' \
'or "auto"'),
security = record(keys=['-s','--security'],
help='security type. "auto" (default), "ssl", or "tls"'),
reference = record(keys = ['-r'],
help = 'display the reference manual'),
login = record(keys = ['-l', '--login'],
help = 'MTA user login name'),
passfile = record(keys = ['-p', '--passfile'],
help = 'name of file containing MTA user password'),
host = record(keys = ['-t', '--host'],
help = 'MTA host specification in <host>:<port> format'),
)):
if myname is None:
myname = os.path.basename(args[0])
parser = OptionParser()
parser.add_option(*opts.encrypt.keys, dest="encrypt", default='true',
help=opts.encrypt.help, choices=('true', 'auto', 'false'))
def callback(opt, opt_str, value, parser, *args, **kwargs):
print reference(myname, opts)
sys.exit(0)
parser.add_option(*opts.reference.keys, dest="reference", action="callback",
callback=callback, help=opts.reference.help)
parser.add_option(*opts.host.keys, dest="host", metavar="HOST",
help=opts.host.help)
parser.add_option(*opts.login.keys, dest='login', metavar="USER",
help=opts.login.help)
parser.add_option(*opts.passfile.keys, dest='passfile', metavar="FILENAME",
help=opts.passfile.help)
parser.add_option(*opts.security.keys, dest="security", default='auto',
metavar='SECURITY', help=opts.security.help, choices=('auto', 'ssl', 'tls'))
options, args = parser.parse_args()
if not options.host:
raise RuntimeError, "no host specified"
if not options.login:
options.login = os.environ.get('USER',
os.environ.get('SMTP_USER', None))
if options.passfile:
options.pswd = open(os.path.abspath(options.passfile), 'r').read()
if not options.login and options.pswd:
raise RuntimeError, 'password provided, but no username provided'
try:
options.host, options.port = options.host.split(':')
except ValueError:
options.port = None
options.conns = [smtp]
security = options.security.lower()
if security == 'auto':
options.sconns = [smtp_ssl, smtp_ssl_fixed, smtp_tls]
elif security == 'ssl':
options.sconns = [smtp_ssl, smtp_ssl_fixed]
elif security == 'tls':
options.sconns = [smtp_tls]
encrypt = options.encrypt
if encrypt == 'false':
if security != 'auto':
raise RuntimeError, \
"if security is specified, encryption cannot be false"
options.encrypt = False
elif encrypt == 'auto':
options.encrypt = 'auto'
else:
options.encrypt = True
return options, args
def connect(conns, opts):
for tryme in conns:
logging.info('trying to connect with %s' % tryme)
try:
return tryme(opts)
except:
logging.info(traceback.format_exc())
def smtp(opts):
smtp = SMTP(opts.host, getattr(opts, 'port', SMTP_PORT))
login(smtp, opts)
def smtp_ssl(opts):
port = opts.port
if port is None:
port = SMTP_SSL_PORT
smtp = SMTP_SSL(opts.host,port)
login(smtp, opts)
return smtp
def smtp_ssl_fixed(opts):
'''
http://bugs.python.org/issue4066
'''
import ssl
import socket
from smtplib import SSLFakeFile
class SMTP_SSL_FIXED(SMTP_SSL):
def _get_socket(self, host, port, timeout):
SMTP_SSL._get_socket(self, host,port, timeout)
return self.sock
port = opts.port
if port is None:
port = SMTP_SSL_PORT
smtp = SMTP_SSL_FIXED(opts.host,port)
login(smtp, opts)
return smtp
def smtp_tls(opts):
if opts.port is None:
ports = (SMTP_PORT, 587)
else:
ports = (opts.port,)
for port in ports:
logging.info('trying to connect to %s on port %s' % (opts.host, port))
smtp = SMTP(opts.host,port)
try:
smtp.starttls()
login(smtp, opts)
except:
logging.info(traceback.format_exc())
else:
break
return smtp
def login(smtp, opts):
smtp.login(opts.login, opts.pswd)
return smtp
def main(stdin=sys.stdin, args=sys.argv, conns=None, sconns=None):
opts, args = options(args)
if conns is None:
conns = opts.conns
if sconns is None:
sconns = opts.sconns
mail = stdin.read()
mail2 = email.message_from_string(mail)
sender = mail2['from']
if not sender:
msg = '"From" should be specified either on the command-line ' \
'or in the message headers'
raise RuntimeError, msg
args = set(args)
conn = None
if opts.encrypt is not False:
conn = connect(sconns,opts)
if not conn:
if opts.encrypt is True:
raise RuntimeError, 'could not establish encrypted connection'
else:
conn = connect(conns, opts)
conn.sendmail(sender, args, mail)
conn.quit()
if __name__ == "__main__":
main()