#!/usr/bin/env python
#
# Copyright (C) 2001,2002 Jason R. Mastaler <jason@mastaler.com>
#
# This file is part of TMDA.
#
# TMDA 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 2 of the License, or
# (at your option) any later version.  A copy of this license should
# be included in the file COPYING.
#
# TMDA 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 TMDA; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

"""Tag and send outgoing messages.

Usage:  %(program)s [OPTIONS] [ recip ... ]

OPTIONS:
	-h
	--help
	   Print this help message and exit.

        -V
        --version
           Print TMDA version information and exit.
           
	-c <file>
	--config-file <file>
	   Specify a different configuration file other than ~/.tmda/config.

        -f <sender>
           This option is silently ignored currently.

        -q
        --qfilter
           Return 99 to qfilter so it will not run qmail-queue itself.

        -O <file>
        --filter-outgoing-file <file>
           Full pathname to your outgoing filter file.  Overrides
           FILTER_OUTGOING in ~/.tmda/config.

        -M <recipient> <sender>
        --filter-match <recipient> <sender>
           Check whether the given e-mail address matches a line in
           your outgoing filter and then exit.  The first address
           given should be the message recipient, and the second is
           the sender (you).  This option will also check for parsing
           errors in the filter file.
           
	recip
	   If one or more recipients are provided, send the message to
	   all recip arguments.  If none are provided, send the message
	   to all header recipient addresses.
"""

import getopt
import os
import sys

try:
    import paths
except ImportError:
    # Prepend /usr/lib/python2.x/site-packages/TMDA/pythonlib
    sitedir = os.path.join(sys.prefix, 'lib', 'python'+sys.version[:3],
                           'site-packages', 'TMDA', 'pythonlib')
    sys.path.insert(0, sitedir)

from TMDA import Version


filter_match = None
qfilter = None
program = sys.argv[0]

def usage(code, msg=''):
    print __doc__ % globals()
    if msg:
        print msg
    sys.exit(code)
    
try:
    opts, args = getopt.getopt(sys.argv[1:],
                               'c:O:M:qVhf:', ['config-file=',
                                               'filter-outgoing-file=',
                                               'filter-match=',
                                               'qfilter',
                                               'version',
                                               'help'])
except getopt.error, msg:
    usage(1, msg)

for opt, arg in opts:
    if opt in ('-h', '--help'):
        usage(0)
    if opt == '-V':
        print Version.ALL
        sys.exit()
    if opt == '--version':
        print Version.TMDA
        sys.exit()
    elif opt in ('-M', '--filter-match'):
	filter_match = 1
    elif opt in ('-O', '--filter-outgoing-file'):
	os.environ['TMDA_FILTER_OUTGOING'] = arg
    elif opt in ('-c', '--config-file'):
        os.environ['TMDARC'] = arg
    elif opt in ('-q', '--qfilter'):
        qfilter = 99
    elif opt == '-f':
        pass


from TMDA import Defaults
from TMDA import Cookie
from TMDA import FilterParser
from TMDA import Util


import email
import string


# Just check Defaults.FILTER_OUTGOING for syntax errors and possible
# matches, and then exit.
if filter_match:
    sender = sys.argv[-1]
    recip = sys.argv[-2]
    Util.filter_match(Defaults.FILTER_OUTGOING, recip, sender)
    sys.exit()

msgout = email.message_from_file(sys.stdin)
orig_msgout_as_string = msgout.as_string()
orig_msgout_size = len(orig_msgout_as_string)
orig_msgout_body_as_raw_string = Util.body_as_raw_string(msgout)

def message_format(fsa, ffn, type):
    # "angles" is the default MESSAGE_FROM_STYLE
    if type == 'address':
        return email.Utils.formataddr((None, fsa))
    else:
        return email.Utils.formataddr((ffn, fsa))

def make_field(cookie_type, cookie_option, from_address, to_address):
    """
    Create an email address based on a cookie type and one or more addresses.
    """
    if cookie_type == 'default':
	cookie_type = Defaults.ACTION_OUTGOING

    if cookie_type == 'bare':
	# Use an untagged address.
	field = from_address
	# Optionally append the recipient address to a file.
	if (cookie_option and string.lower(cookie_option) == 'append'
	    and Defaults.BARE_APPEND):
	    Util.append_to_file(to_address, Defaults.BARE_APPEND)
    elif cookie_type == 'dated':
	# Send a message with a tagged (dated) address.
	if cookie_option:               # check for timeout override
	    os.environ['TMDA_TIMEOUT'] = cookie_option
        else:
            os.environ['TMDA_TIMEOUT'] = ''
	field = Cookie.make_dated_address(from_address)
    elif cookie_type == 'sender':
	# Send a message with a tagged (sender) address
	sender_cookie_address = cookie_option or to_address
	field = Cookie.make_sender_address (from_address, sender_cookie_address)
    elif cookie_type in ('as','exp','explicit') and cookie_option:
	# Send a message with an explicitly defined address.
	field = cookie_option
    elif cookie_type in ('ext','extension') and cookie_option:
	# Send a message with a tagged (extension added) address.
	(username, hostname) = string.split(from_address,'@')
	field = username + Defaults.RECIPIENT_DELIMITER + \
                cookie_option + '@' + hostname
    elif cookie_type in ('kw','keyword') and cookie_option:
	# Send a message with a tagged (keyword) address.
	field = Cookie.make_keyword_address(from_address, cookie_option)
    elif not cookie_type:
        # cookie_type == None means field is a text string
        field = cookie_option
    else:
	# If cookie_type is invalid, punt and use an untagged address.
	field = from_address
    return field


def inject_message(resending,
                   to_address,
                   from_address,
		   full_name,
                   msg,
                   orig_msgout_body_as_raw_string,
                   actions,
                   log_msg):
    """Hand the message off to sendmail."""
    # Default, if no From: is specified, is bare.
    (cookie_type, cookie_option) = actions.get('from', ('bare', None))
    magic_from = make_field(cookie_type, cookie_option,
                            from_address, to_address)
    envelope_sender = resent_from = magic_from
    # Update envelope_sender with user-specified header
    (cookie_type, cookie_option) = actions.get('envelope', (None, None))
    if cookie_type:
	envelope_sender = make_field(cookie_type, cookie_option,
                                     from_address, to_address)
    # Update resent_from with user-specified header
    (cookie_type, cookie_option) = actions.get('resent-from', (None, None))
    if cookie_type:
	resent_from = make_field(cookie_type, cookie_option,
                                 from_address, to_address)
    # Set From: or Resent-From: to match the envelope sender address.
    if resending:
        del msg['Resent-From']
	msg['Resent-From'] = message_format(resent_from, full_name,
                                            Defaults.MESSAGE_FROM_STYLE)
    else:
        del msg['From']
	msg['From'] = message_format(magic_from, full_name,
                                     Defaults.MESSAGE_FROM_STYLE)
    # If the MUA has added a `Mail-Followup-To' header that contains
    # the untagged address, we need to tag that as well.
    if msg.has_key('mail-followup-to'):
	mft_list = email.Utils.getaddresses(msg.get_all('mail-followup-to'))
	new_mft_list = []
	for a in mft_list:
	    emaddy = a[1]
	    if emaddy == from_address:
		new_mft_list.append(magic_from)
	    else:
		new_mft_list.append(emaddy)
        del msg['Mail-Followup-To']
	msg['Mail-Followup-To'] = string.join(new_mft_list, ',\n\t')
    # Add `Date' and `Message-ID' headers only if they don't already exist.
    if not msg.has_key('date'):
	msg['Date'] = Util.make_date()
    if not msg.has_key('message-id'):
	msg['Message-ID'] = Util.make_msgid()
    # Add `X-Delivery-Agent' header.
    del msg['X-Delivery-Agent']
    msg['X-Delivery-Agent'] = 'TMDA/%s (%s)' % (Version.TMDA, Version.CODENAME)
    # Optionally, add an `X-TMDA-Fingerprint' header.
    if Defaults.FINGERPRINT:
	hdrlist = []
	for hdr in Defaults.FINGERPRINT:
            if hdr == 'body':
                hdrval = orig_msgout_body_as_raw_string
            else:
                hdrval = msg.get(hdr)
	    if hdrval:
		hdrlist.append(hdrval)
	if hdrlist:
            del msg['X-TMDA-Fingerprint']
	    msg['X-TMDA-Fingerprint'] = (Cookie.make_fingerprint(hdrlist))
    # Optionally, add some headers.
    if Defaults.ADDED_HEADERS_CLIENT:
        for hdr in Defaults.ADDED_HEADERS_CLIENT.keys():
            del msg[hdr]
            msg[hdr] = Defaults.ADDED_HEADERS_CLIENT[hdr]
    # Optionally, remove some headers.
    if Defaults.PURGED_HEADERS:
	for hdr in Defaults.PURGED_HEADERS:
	    del msg[hdr]
    # Create the custom headers.
    custom_headers = [ h for h in actions.keys()
                        if h not in ('from', 'envelope', 'resent-from') ]
    nice_headers = []
    for header in custom_headers:
        (cookie_type, cookie_option) = actions[header]
        field = make_field(cookie_type, cookie_option, from_address, to_address)
        if cookie_type:
            field = message_format(field, full_name,
                                   Defaults.MESSAGE_TAG_HEADER_STYLE)
        nice_header = string.capwords(header.replace('-', ' ')).replace(' ', '-')
        nice_headers.append((nice_header, field))
    if nice_headers:
        for h, v in nice_headers:
            del msg[h]
            msg[h] = v
    # Optionally, log this transmission.
    if Defaults.LOGFILE_OUTGOING:
        from TMDA import MessageLogger
        logger = MessageLogger.MessageLogger(Defaults.LOGFILE_OUTGOING,
                                             msg,
                                             envsender = envelope_sender,
                                             envrecip = to_address,
                                             msg_size = orig_msgout_size,
                                             action_msg = log_msg)
        logger.write()
    # Inject the message.
    Util.sendmail(msg.as_string(), to_address, envelope_sender)


######
# Main
######

def main():

    x_tmda_over = None
    actions = None
    log_msg = None

    if msgout.has_key('resent-from'):
        # We must be resending (bouncing) the message.
        fullname, from_address = email.Utils.parseaddr(msgout.get
                                                       ('resent-from'))
        resending = 1
    else:
        # Use the existing From: header if possible.
        fullname, from_address = email.Utils.parseaddr(msgout.get('from'))
        resending = None
    if not fullname:
        fullname = Defaults.FULLNAME
    if not from_address or len(string.split(from_address,'@')) != 2:
        from_address = Defaults.USERNAME + '@' + Defaults.HOSTNAME
        
    # If recipients were provided as arguments, use them.
    if args:
        address_list = args
    # If running through qfilter, get recipient list from QMAILRCPTS.
    elif os.environ.has_key('QMAILRCPTS'):
        address_list = string.split(string.lower
                                    (os.environ['QMAILRCPTS']),'\n')[:-1]
    # Otherwise get recipients from the headers.
    else:
        address_list = []
        if resending:
            # Use Resent-To, Resent-Cc, and Resent-Bcc addresses.
            resent_tos = msgout.get_all('resent-to', [])
            resent_ccs = msgout.get_all('resent-cc', [])
            resent_bccs = msgout.get_all('resent-bcc', [])
            header_pairs = email.Utils.getaddresses(resent_tos +
                                                    resent_ccs +
                                                    resent_bccs)
        else:
            # Use To, Cc, Bcc, and Apparently-To addresses.
            tos = msgout.get_all('to', [])
            ccs = msgout.get_all('cc', [])
            bccs = msgout.get_all('bcc', [])
            apparently_tos = msgout.get_all('apparently-to', [])
            header_pairs = email.Utils.getaddresses(tos + ccs +
                                                    bccs + apparently_tos)
        for pair in header_pairs:
            address = pair[1]
            address_list.append(address)

    # Check for the `X-TMDA' override header.
    if msgout.has_key('x-tmda'):
        x_tmda_over = 1
        x_tmda = msgout.get('x-tmda')
        log_msg = '%s: %s' % ('X-TMDA', x_tmda)
        # X-TMDA should only have one field.
        if len(string.split(x_tmda)) == 1:
	    actions = { 'from' : FilterParser.splitaction(x_tmda) }
        # Delete `X-TMDA' before sending.
        del msgout['x-tmda']
    # Optionally, parse subject for `X-TMDA'.
    if (Defaults.X_TMDA_IN_SUBJECT and msgout.has_key('subject') and
        x_tmda_over is None):
        sub = msgout.get('subject')
        subsplit = sub.split(None, 2)
        if subsplit and subsplit[0].lower() == 'x-tmda':
            x_tmda_over = 1
            actions = { 'from' : FilterParser.splitaction(subsplit[1]) }
            log_msg = '%s: %s' % ('X-TMDA', subsplit[1])
            # Fixup Subject: before sending.
            del msgout['Subject']
            if subsplit[2:]:
                msgout['Subject'] = subsplit[2:][0]
            else:
                msgout['Subject'] = ''
    # Without `X-TMDA', we need to parse the outgoing filter file.
    if x_tmda_over is None:
        outfilter = FilterParser.FilterParser()
        outfilter.read(Defaults.FILTER_OUTGOING)

    # If the address matches a line in the filter file, it is tagged
    # accordingly, otherwise it is tagged with the default cookie
    # type.
    for address in address_list:
        # If `X-TMDA' is present we are done here.
        if x_tmda_over:
            pass                        
        else:
            (actions, matching_line) = outfilter.firstmatch(address,
                                                            [from_address])
            log_msg = matching_line
        if not actions:
	    actions = {
                'from' : FilterParser.splitaction(Defaults.ACTION_OUTGOING) }
            log_msg = '%s (%s)' % ('action_outgoing', Defaults.ACTION_OUTGOING)
        # The message is sent to each recipient separately so that
        # everyone gets the correct tag.  Make sure your MUA
        # generates its own Message-ID: and Date: headers so they
        # match on multiple recipient messages.
        inject_message(resending,
                       address,
                       from_address,
		       fullname,
                       msgout,
                       orig_msgout_body_as_raw_string,
                       actions,
                       log_msg)
    if qfilter:
        sys.exit(qfilter)
    else:
        sys.exit(Defaults.EX_OK)


# This is the end my friend.
if __name__ == '__main__':
    main()
