#! /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()

Pub/SendmailPy (last edited 2010-02-21 13:24:54 by pooryorick)