#!/usr/bin/python

##############################################################################
# BACKUP_MAILER.PY - Emails a file to a given address. Can split large
#                    files across multiple messages and wait between each 
#                    chunk to avoid overloading the server.
#                    vrai@acherondevelopment.com
#
# Published under the GNU Public License: http://www.gnu.org/copyleft/gpl.html
#
# $Id: backup_mailer.py 740 2005-06-23 09:52:00Z vrai $


##############################################################################
# Functions 

import smtplib, sys, getpass, os.path, poplib, getopt, time;
import email.MIMEMultipart, email.MIMEText, email.MIMEBase, email.Encoders;


def ShowUsage ( ):
	sys.exit (
"""
backup_mailer.py -f FILE -t TO_ADDR -s FROM_ADDR -h MAIL_SERVER
                 -b BYTES_PER_MSG -d DELAY_SECS [ -p POPUSER:POPPASS ]
                 [ -a SMTPUSER:SMTPPASS ]

    -f x    File to backup
    -t x    Email address to send backup message(s) to
    -s x    Email address to use in 'From:' field
    -h x    Mail server to use
    -b x    Maximum size of attachment in each message
    -d x    Delay between messages in seconds
    -p x:y  Perform POP3 authentication using this uid/pwd, colon seperated
    -a x:y  Perform SMTP authentication using this uid/pwd, colon seperated

    If a password field (in -p and -a) is empty then the user will be
    prompted to enter the password.

    Both POP3 authentication (if used) will be done before each message
    (i.e. each segment of the file) is sent.
"""
             );


def GetCommandLineArguments ( arguments ):
	try:
		options, arguments = getopt.getopt ( arguments, "h:f:t:s:b:p:a:d:" );
	except getopt.GetoptError, exception:
		sys.stderr.write ( "Error: %s\n" % str ( exception ).strip ( ) );
		ShowUsage ( );

	switchNameMap = [ ( "-%s" % switch, name ) for switch, name in
	                  [ [ 'f', 'file' ], [ 't' , 'recipient' ], [ 's', 'sender' ], [ 'h', 'server' ], [ 'b', 'size' ],
					    [ 'p', 'pop' ], [ 'a', 'smtp' ], [ 'd', 'delay' ] ] ];
	requiredValues = [ 'file', 'recipient', 'sender', 'size', 'server', 'delay' ];
	values = { };

	for option, value in options:
		for switch, name in switchNameMap:
			if option == switch:
				values [ name ] = value;
	
	if [ key for key in requiredValues if not key in values ]:
		ShowUsage ( );
	else:
		return values;


def GenerateTag ( index, width = 2 ):
	if index > ( 26 ** width ):
		sys.exit ( "Filename tags are set to %d character - no more than %d elements" % ( width, 26 ** width ) );
	else:
		tag = [ ];
		while width > 0:
			char = index % 26;
			tag += chr ( 0x61 + char );
			index /= 26;
			width -=1;
		tag.reverse ( );
		return ''.join ( tag );

		
def LoadFile ( filename, size ):
	parts = [ ];
	try:
		handle = file ( filename, 'r' );
		while ( True ):
			section = handle.read ( int ( size ) );
			if not section:
				break;
			else:
				parts += [ [ '', section ] ];
	except IOError, error:
		sys.exit ( "Unable to read in file \"%s\" - %s\n\n" % ( filename, str ( error ).strip ( ) ) );
	
	filename = os.path.basename ( filename );
		
	if len ( parts ) < 1:
		sys.exit ( "File \"%s\" is empty!" % filename );
	elif len ( parts ) == 1:
		parts [ 0 ] [ 0 ] = filename;
	else:
		for index in range ( len ( parts ) ):
			parts [ index ] [ 0 ] = "%s.%s" % ( filename, GenerateTag ( index = index ) );

	return parts;


def GetUsernamePassword ( type, server, pair ):
	try:
		username, password = [ element.strip ( ) for element in pair.split ( ':', 1 ) ];
		if not password:
			password = getpass.getpass ( "Please enter %s password for %s: " % ( type, server ) );
		return username, password;
	except Exception:
		sys.exit ( "Unable to parse username/password pair for %s field. Pair \"%s\" is not valid" % ( server, pair ) );


def BuildComplexMail ( subject, sender, body = None, to = None, cc = None, attach = { } ):
	message = email.MIMEMultipart.MIMEMultipart ( );
	message [ 'Subject' ] = subject;
	message [ 'From' ] = sender;
	message [ 'X-Sender' ] = 'mailer.py';

	if to: message [ 'To' ] = ', '.join ( to );
	if cc: message [ 'Cc' ] = ', '.join ( cc );

	message.epilogue = '';

	if body:
		message.attach ( email.MIMEText.MIMEText ( body ) );

	for ( filename, filecontent ) in attach.items ( ):
		part = email.MIMEBase.MIMEBase ( 'application', 'octet-stream' );
		part.set_payload ( filecontent );
		email.Encoders.encode_base64 ( part );
		part.add_header ( 'Content-Disposition', 'attachment', filename = filename );
		message.attach ( part );

	return message.as_string ( );


def SendMail ( host, port, message, sender, pair = None, to = None, cc = None, bcc = None ):
	if pair:
		username, password = pair;
	else:
		username, password = ( None, None );

	recipients = [ ];
	if to: recipients += to;
	if cc: recipients += cc;
	if bcc: recipients += bcc;

	if len ( recipients ):
		messagesize = len ( message );
		print "Sending message ( %d bytes ) to %s using server %s:%d" % ( messagesize, 
			  ', '.join ( recipients ), host, port );
		smtp = smtplib.SMTP ( );
		smtp.connect ( host, port );
		if username: smtp.login ( username, password );
		starttime = time.time ( );
		smtp.sendmail ( sender, recipients, message );
		duration = time.time ( ) - starttime;
		smtp.close ( );
		print 'Sent %d bytes in %.1f seconds, %d bytes per second' % ( messagesize, duration, float ( messagesize ) / duration );


def AuthenticatePOP ( host, port, pair ):
	try:
		username, password = pair;

		print "Authenticating with POP3 server %s:%d" % ( host, port );
		pop3 = poplib.POP3 ( host, port );
		pop3.user ( username );
		pop3.pass_ ( password );
		pop3.noop ( );
		pop3.quit ( );
	except Exception, exception:
		sys.exit ( "Unable to authenticate with POP server %s - %s\n" % ( host, str ( exception ).strip ( ) ) );


def Main ( arguments ):
	settings = GetCommandLineArguments ( arguments );
	fileparts = LoadFile ( filename = settings [ 'file' ], size = settings [ 'size' ] );

	for field, type in [ [ 'pop', 'POP3' ], [ 'smtp', 'SMTP' ] ]:
		if field in settings:
			settings [ field ] = GetUsernamePassword ( type = type, server = settings [ 'server' ], pair = settings [ field ] );

	messages = [ [ name, BuildComplexMail ( subject = name,
			                                sender = settings [ 'sender' ],
											body = "See attached - %d bytes" % len ( content ),
			                                to = [ settings [ 'recipient' ] ],
											attach = { name : content }
										  ) ] for name, content in fileparts ];

	if 'smtp' in settings:
		smtp = settings [ 'smtp' ];
	else:
		smtp = None;
	
	for index in range ( len ( messages ) ):
		if 'pop' in settings:
			AuthenticatePOP ( host = settings [ 'server' ], port = 110, pair = settings [ 'pop' ] );

		name, message = messages [ index ];
		SendMail ( host = settings [ 'server' ],
				   port = 25,
				   pair = smtp,
				   to = [ settings [ 'recipient' ] ],
				   message = message,
				   sender = settings [ 'sender' ] );
		if ( index + 1 ) < len ( messages ):
			print "Waiting %s seconds" % settings [ 'delay' ];
			time.sleep ( int ( settings [ 'delay' ] ) );

		
##############################################################################
# Script entry point

if __name__ == '__main__':
	Main ( sys.argv [ 1: ] );

