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

"""Filter incoming messages on standard input.

Usage:  %(program)s [OPTIONS]

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 ~/.tmdarc.

        -d
	--discard
	   Discard message if address is invalid instead of bouncing it.

        -t <dir>
        --template-dir <dir>
	   Full pathname to a directory containing custom TMDA templates.

        -I <file>
        --filter-incoming-file <file>
           Full pathname to your incoming filter file.  Overrides FILTER_INCOMING
           in ~/.tmdarc.
           
        -M <recipient> <sender>
        --filter-match <recipient> <sender>
           Check whether the given e-mail addresses match a line in your incoming
           filter and then exit.  The first address given should be the message
           recipient (you), and the second is the sender.  This option will also
           check for parsing errors in the filter file.
"""

import getopt
import os
import sys

try:
    import paths
except ImportError:
    pass

from TMDA import Version


filter_match = None
discard = 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:dt:I:M:Vh', ['config-file=',
                                               'discard',
                                               'template-dir=',
                                               'filter-incoming-file=',
                                               'filter-match=',
                                               '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 ('-I', '--filter-incoming-file'):
	os.environ['TMDA_FILTER_INCOMING'] = arg
    elif opt in ('-t', '--template-dir'):
        os.environ['TMDA_TEMPLATE_DIR'] = arg
    elif opt in ('-d', '--discard'):
	discard = 1
    elif opt in ('-c', '--config-file'):
        os.environ['TMDARC'] = arg


from TMDA import Defaults
from TMDA import Deliver
from TMDA import Cookie
from TMDA import Errors
from TMDA import FilterParser
from TMDA import MTA
from TMDA import Util


import cStringIO
import fileinput
import rfc822
import string
import time


# Just check Defaults.FILTER_INCOMING 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_INCOMING, recip, sender)
    sys.exit()
    
# We use this MTA instance to control the fate of the message.
mta = MTA.init()

# Read sys.stdin into a temporary variable for later access.
stdin = cStringIO.StringIO(sys.stdin.read())

# Collect the message headers.
message_headers = rfc822.Message(stdin)
# Collect the message body.
message_body = stdin.read()

# Collect the entire message.
message = stdin.getvalue()
# Calculate the message size.
message_size = str(len(message_body))

# Collect the three essential environment variables, and defer if they
# are missing.

# SENDER is the envelope sender address.
envelope_sender = os.environ.get('SENDER')
if envelope_sender == None:
    raise Errors.MissingEnvironmentVariable('SENDER')
# RECIPIENT is the envelope recipient address.
# Use Defaults.RECIPIENT_HEADER instead if set.
recipient_header = None
if Defaults.RECIPIENT_HEADER:
    recipient_header = message_headers.getheader(Defaults.RECIPIENT_HEADER)
envelope_recipient = (recipient_header or os.environ.get('RECIPIENT'))
if envelope_recipient == None:
    raise Errors.MissingEnvironmentVariable('RECIPIENT')
# EXT is the recipient address extension.
address_extension = (os.environ.get('EXT')           # qmail
                     or os.environ.get('EXTENSION')) # Postfix

# If SENDER exists but its value is empty, the message has an empty
# envelope sender.  Set it to the string '<>' so it can be matched as
# such in the filter files.
if envelope_sender == '':
    envelope_sender = '<>'
# If running Sendmail, make sure envelope_sender contains a fully
# qualified address by appending the local hostname if necessary.
# This is often the case when the message is sent between local users
# on a Sendmail system.
elif (Defaults.MAIL_TRANSFER_AGENT == 'sendmail' and
      len(string.split(envelope_sender,'@')) == 1):
    envelope_sender = envelope_sender + '@' + Util.gethostname()
# Ditto for envelope_recipient
if (Defaults.MAIL_TRANSFER_AGENT == 'sendmail' and
    len(string.split(envelope_recipient,'@')) == 1):
    envelope_recipient = envelope_recipient + '@' + Util.gethostname()

# recipient_address is the original address the message was sent to,
# not qmail-send's rewritten interpretation.  This will be the same as
# envelope_recipient if we are not running under a qmail virtualdomain.
recipient_address = envelope_recipient
if (Defaults.MAIL_TRANSFER_AGENT == 'qmail' and
    Defaults.USEVIRTUALDOMAINS and
    os.path.exists(Defaults.VIRTUALDOMAINS)):
    # Parse the virtualdomains control file; see qmail-send(8) for
    # syntax rules.  All this because qmail doesn't store the original
    # envelope recipient in the environment.
    (ousername, odomain) = string.split(envelope_recipient,'@')
    for line in fileinput.input(Defaults.VIRTUALDOMAINS):
        vdomain_match = 0
        line = string.lower(string.strip(line))
        # Comment or blank line?
        if line == '' or line[0] in '#':
            continue
        else:
            (vdomain, prepend) = string.split(line,':')
            # domain:prepend
            if vdomain == string.lower(odomain):
                vdomain_match = 1
            # .domain:prepend (wildcard)
            elif not string.split(vdomain,'.',1)[0]:
                if string.find(string.lower(odomain), vdomain) != -1:
                    vdomain_match = 1
            # user@domain:prepend
            else:
                try:
                    if string.split(vdomain,'@')[1] == string.lower(odomain):
                        vdomain_match = 1
                except IndexError:
                    pass
            if vdomain_match:
                # strip off the prepend
                if prepend:
                    nusername = string.replace(ousername,prepend + '-','')
                    recipient_address = nusername + '@' + odomain
                    # also strip off the prepend and the virtual
                    # username from address_extension
                    address_extension = (
                        string.join(string.split
                                    (nusername,
                                     Defaults.RECIPIENT_DELIMITER)[1:],
                                    Defaults.RECIPIENT_DELIMITER))
                    fileinput.close()
                    break

# Collect the message's Subject: for later use.
subject = message_headers.getheader('subject', 'None')

# The directory of pending messages.
pendingdir = os.path.join(Defaults.DATADIR, 'pending')

# Collect the message's Precedence: header.
precedence = message_headers.getheader('precedence', None)
# If its value is "bulk", "junk", or "list" we should not generate any
# auto-replies.
auto_reply = 1
if precedence and string.lower(precedence) in ('bulk','junk','list'):
    auto_reply = 0
# Don't generate an auto-reply if the message contains any of the
# List-XXX: mailing list headers defined in rfc2369, nor
# `Mailing-List'.
list_headers = ['list-help', 'list-subscribe', 'list-unsubscribe',
                'list-post', 'list-owner', 'list-archive', 'mailing-list']
for hdr in list_headers:
    if message_headers.has_key(hdr):
        auto_reply = 0
        break


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

def logit(action_msg, timesecs=None):
    """Write delivery statistics to the logfile if it's enabled."""
    if Defaults.LOGFILE_INCOMING and recipient_address:
        if not timesecs:
            timesecs = time.time()
        logfile = open(Defaults.LOGFILE_INCOMING, 'a') # append to the file
        Date = Util.unixdate(timesecs)
        From = message_headers.getheader('from')
        EnvelopeSender = envelope_sender
        To = recipient_address
        Subject = subject
        Action = action_msg
        actionstr = 'Actn: ' + Action
        sizestr = '(' + message_size + ')'
        wsbuf = 78 - len(actionstr) - len(sizestr)
        # Write the log entry and then close the log.
        logfile.write('Date: ' + Date + '\n')
        if (EnvelopeSender
            and message_headers.getaddr('from')[1] != EnvelopeSender):
            logfile.write('Sndr: ' + EnvelopeSender + '\n')
        if From:
            logfile.write('From: ' + From + '\n')
        logfile.write('  To: ' + To + '\n')
        logfile.write('Subj: ' + Subject + '\n')
        logfile.write(actionstr + ' '*wsbuf + sizestr + '\n')
        logfile.write('\n')
        logfile.close()


def send_bounce(bounce_message, **vars):
    """Send a confirmation message back to the sender."""
    if auto_reply:
        bounce_message = cStringIO.StringIO(bounce_message)
        message_headers = rfc822.Message(bounce_message)
        # Add some headers.
        timesecs = time.time()
        message_headers['Date'] = Util.make_date(timesecs)
        message_headers['Message-ID'] = Util.make_msgid(timesecs)
        message_headers['To'] = envelope_sender
        if not vars.has_key('already_confirmed'):
            message_headers['Reply-To'] = vars['confirm_accept_address']
        message_headers['Precedence'] = 'bulk'
        message_headers['X-Delivery-Agent'] = Version.ALL
        message_body = bounce_message.read()
        Util.sendmail(message_headers, message_body,
                      envelope_sender, Defaults.BOUNCE_ENV_SENDER)


def send_cc(address):
    """Send a 'carbon copy' of the message to address."""
    Util.sendmail(message_headers, message_body, address)
    logit('CC ' + address)


def release_pending(timestamp, pid, message_headers, message_body):
    """Release a confirmed message from the pending queue."""
    return_path = message_headers.getaddr('return-path')[1]
    confirm_done_address = Cookie.make_confirm_address(
        message_headers.getheader('x-tmda-recipient'), timestamp, pid, 'done')
    del message_headers['x-tmda-recipient']
    # Add the date when confirmed in a header.
    message_headers['X-TMDA-Confirmed'] = Util.unixdate()
    # Reinject the message.
    Util.sendmail(message_headers, message_body,
                  confirm_done_address, return_path)
    mta.stop()


def verify_confirm_cookie(confirm_cookie):
    """Verify a confirmation cookie."""
    # Save some time if the cookie is bogus.
    try:
        (confirm_action, confirm_timestamp,
         confirm_pid, confirm_hmac) = confirm_cookie.split('.')
    except ValueError:
        logit("BOUNCE invalid_confirmation_address")
        bouncegen('bounce', Defaults.BOUNCE_TEXT_INVALID_CONFIRMATION)
    confirmed_filename = '%s.%s.msg' % (confirm_timestamp, confirm_pid)
    confirmed_file = os.path.join(pendingdir, confirmed_filename)
    if os.path.exists(Defaults.DELIVERED_CACHE):
        delcache = Util.unpickle(Defaults.DELIVERED_CACHE)
    else:
        delcache = []
    # Determine whether this message has already been delivered, and
    # if by manual release, or confirmation.
    delivered = None
    for dict in delcache:
        if dict.has_key(confirmed_filename):
            # 'c' for confirmed, 'r' for released
            delivered = dict[confirmed_filename]
            break
    # pre-confirmation
    if confirm_action == 'accept':
        new_confirm_hmac = Cookie.confirmationmac(confirm_timestamp,
                                                  confirm_pid, 'accept')
        # Accept the message only if the HMAC can be verified.
        if not (confirm_hmac == new_confirm_hmac):
            logit("BOUNCE invalid_confirmation_address")
            bouncegen('bounce', Defaults.BOUNCE_TEXT_INVALID_CONFIRMATION)
        # If the message isn't recorded as delivered and doesn't exist,
        # alert sender that their original is missing.
        if not delivered and not (os.path.exists(confirmed_file)):
            logit("BOUNCE nonexistent_pending_message")
            bouncegen('bounce', Defaults.BOUNCE_TEXT_NONEXISTENT_PENDING)
        logit("CONFIRM accept " + confirmed_filename)
        # Optionally carbon copy the confirmation to another address.
        if Defaults.CONFIRM_ACCEPT_CC:
            send_cc(Defaults.CONFIRM_ACCEPT_CC)
        if os.path.exists(confirmed_file):
            fp = open(confirmed_file, 'r')
            message_headers = rfc822.Message(fp)
            message_body = fp.read()
            fp.close()
            # Optionally append the envelope sender to a file
            if Defaults.CONFIRM_APPEND:
                return_path = message_headers.getaddr('return-path')[1]
                if return_path is None:
                    raise IOError, \
                          confirmed_file + ' has no Return-Path header!'
                if Util.append_to_file(return_path,
                                       Defaults.CONFIRM_APPEND) != 0:
                    logit('CONFIRM_APPEND ' + Defaults.CONFIRM_APPEND)
        # Optionally generate a confirmation acceptance notice.
        if Defaults.CONFIRM_ACCEPT_NOTIFY:
            if (delivered == 'c' and
                Defaults.CONFIRM_ACCEPT_TEXT_ALREADY_CONFIRMED):
                bouncegen('accept',
                          Defaults.CONFIRM_ACCEPT_TEXT_ALREADY_CONFIRMED)
            elif (delivered == 'r' and
                  Defaults.CONFIRM_ACCEPT_TEXT_ALREADY_RELEASED):
                bouncegen('accept',
                          Defaults.CONFIRM_ACCEPT_TEXT_ALREADY_RELEASED)
            elif not delivered:
                bouncegen('accept', Defaults.CONFIRM_ACCEPT_TEXT_INITIAL)
        # Just stop if the message has already been delivered.  Also,
        # change the release mark from 'r' to 'c' to note that this
        # message has had a confirmation attempt.
        if delivered:
            if delivered == 'r':
                for dict in delcache:
                    if dict.has_key(confirmed_filename):
                        dict[confirmed_filename] = 'c'
                        Util.pickleit(delcache, Defaults.DELIVERED_CACHE)
                        break
            mta.stop()
        # Release the message if we get this far.
        release_pending(confirm_timestamp, confirm_pid,
                        message_headers, message_body)
    # post-confirmation
    elif confirm_action == 'done':
        new_confirm_hmac = Cookie.confirmationmac(confirm_timestamp,
                                                  confirm_pid, 'done')
        # Accept the message only if the HMAC can be verified,
        if not (confirm_hmac == new_confirm_hmac):
            logit("BOUNCE invalid_confirmation_address")
            bouncegen('bounce', Defaults.BOUNCE_TEXT_INVALID_CONFIRMATION)
        # and it hasn't already been delivered.
        elif delivered:
            # JRM: shouldn't get here - you are either trying to
            # re-release a message, or someone is playing silly
            # buggers and trying to sneak a message through.
            logit("DROP already_delivered_message")
            mta.stop()
        else:
            # Cache and deliver the message.
            incoming_headers = globals().get('message_headers')
            if incoming_headers.has_key('x-tmda-confirmed'):
                msgval = 'c'
            elif incoming_headers.has_key('x-tmda-released'):
                msgval = 'r'
            # Record this message in the cache as a dictionary,
            # where the message filename is the key, and how it was
            # delivered is the value. e.g, {'1017186412.798.msg':'c'}
            delcache.insert(0, {confirmed_filename:msgval})
            # Trim tail entries off if necessary, and then write the cache.
            delcache = delcache[:Defaults.DELIVERED_CACHE_LEN]
            Util.pickleit(delcache, Defaults.DELIVERED_CACHE)
            logit("OK good_confirm_cookie")
            mta.deliver(message)


def verify_dated_cookie(dated_cookie):
    """Verify a dated cookie."""
    # Save some time if the cookie is bogus.
    dated_cookie_split = string.split(dated_cookie,'.')
    if len(dated_cookie_split) != 2:
        bouncegen('request')
    cookie_date = dated_cookie_split[0]
    datemac = dated_cookie_split[1]
    newdatemac = Cookie.datemac(cookie_date)
    # Accept the message only if the address has not expired *and* the HMAC
    # can be verified.
    now = time.time()
    if ((int(cookie_date) >= int('%d' % now)) and (datemac == newdatemac)):
        logit("OK good_dated_cookie",now)
        mta.deliver(message)
    else:
        bouncegen('request')


def verify_sender_cookie(sender_address,sender_cookie):
    """Verify a sender cookie."""
    sender_address_cookie = Cookie.make_sender_cookie(sender_address)
    # Accept the message only if the HMAC can be verified.
    if (sender_cookie == sender_address_cookie):
        logit("OK good_sender_cookie")
        mta.deliver(message)
    else:
        bouncegen('request')


def verify_keyword_cookie(keyword_cookie):
    """Verify a keyword cookie."""
    parts = string.split(keyword_cookie, '.')
    keyword = string.join(parts[:-1], '.')
    mac = parts[-1:][0]
    newmac = Cookie.make_keywordmac(keyword)
    # Accept the message only if the HMAC can be verified.
    if mac == newmac:
        logit("OK good_keyword_cookie \"" + keyword + "\"")
        mta.deliver(message)
    else:
        bouncegen('request')


def bouncegen(mode, text=None):
    """Bounce a message back to sender."""
    # Stop right away if --discard was specified.
    if discard:
        mta.stop()
    # Common variables.
    now = time.time()
    recipient_address = globals().get('recipient_address')
    (recipient_local, recipient_domain) = recipient_address.split('@')
    envelope_sender = globals().get('envelope_sender')
    subject = globals().get('subject')
    original_message_body = globals().get('message_body')
    original_message_headers = globals().get('message_headers')
    original_message_size = globals().get('message_size')
    # Don't include message bodies over a certain size.
    if (Defaults.CONFIRM_MAX_MESSAGE_SIZE and
        (int(Defaults.CONFIRM_MAX_MESSAGE_SIZE) < int(original_message_size))):
        original_message = "%s\n[ Message body suppressed (exceeded %s bytes) ]"\
                           % (original_message_headers,
                              Defaults.CONFIRM_MAX_MESSAGE_SIZE)
    else:
        original_message = globals().get('message')
    # Optional 'dated' address variables.
    if Defaults.DATED_TEMPLATE_VARS:
        dated_timeout = Util.format_timeout(Defaults.TIMEOUT)
        dated_expire_date = time.asctime(time.gmtime
                                         (now + Util.seconds(Defaults.TIMEOUT)))
        dated_cookie_address = Cookie.make_dated_address(Defaults.USERNAME +
                                                         '@' +
                                                         Defaults.HOSTNAME)
    # Optional 'sender' address variables.
    if Defaults.SENDER_TEMPLATE_VARS:
        sender_cookie_address = Cookie.make_sender_address(Defaults.USERNAME +
                                                           '@' +
                                                           Defaults.HOSTNAME,
                                                           envelope_sender)
    if mode == 'accept':                # confirmation acceptance notices
        templatefile = 'confirm_accept.txt'
        confirm_accept_text = Util.wraptext(text)
    elif mode == 'bounce':              # failure notices
        if text is None:
            mta.stop()
        else:
            templatefile = 'bounce.txt'
            bounce_text = Util.wraptext(text)
    elif mode == 'request':               # confirmation requests
        templatefile = 'confirm_request.txt'
        timestamp = str('%d' %now)
        pid = Defaults.PID
        confirm_accept_address = Cookie.make_confirm_address(recipient_address,
                                                             timestamp,
                                                             pid,
                                                             'accept')
        pending_message = "%s.%s.msg" % (timestamp, pid)
        # Create ~/.tmda/ and friends if necessary.
        if not os.path.exists(pendingdir):
            os.makedirs(pendingdir, 0700) # stores the unconfirmed messages
        # X-TMDA-Recipient is used by release_pending().
        message_headers['X-TMDA-Recipient'] = recipient_address
        # Write ~/.tmda/pending/TIMESTAMP.PID.msg
        pending_contents = str(message_headers) + '\n' + message_body
        Util.writefile(pending_contents,
                       os.path.join(pendingdir, pending_message))
    # Create the message and then send it.
    bounce_message = Util.maketext(templatefile, vars())
    if mode in ('accept', 'bounce'):
        send_bounce(bounce_message, already_confirmed=1)
        if mode == 'bounce':
            mta.stop()
    elif mode == 'request':
        if Defaults.CONFIRM_CC:
            send_cc(Defaults.CONFIRM_CC)
        logit("CONFIRM pending " + pending_message)
        send_bounce(bounce_message,
                    confirm_accept_address = confirm_accept_address)
        mta.stop()  


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

def main():

    # Get the cookie type and value by parsing the extension address.
    ext = address_extension
    if ext:
        ext = string.lower(ext)
        ext_split = string.split(ext, Defaults.RECIPIENT_DELIMITER)
        cookie_value = ext_split[-1]
        try:
            cookie_type = ext_split[-2]
        except IndexError:
            cookie_type = None
        if cookie_type not in ('confirm','dated','sender'):
            cookie_type = 'keyword'
            cookie_value = ext
    else:
        cookie_type = None
        cookie_value = None

    # The list of sender e-mail addresses comes from the envelope
    # sender, the "From:" header and the "Reply-To:" header.
    sender_list = [envelope_sender]
    from_list = message_headers.getaddrlist("from")
    replyto_list = message_headers.getaddrlist("reply-to")
    for list in from_list,replyto_list:
        for a in list:
            emaddy = a[1]
            sender_list.append(emaddy)

    # Process confirmation messages first.
    if cookie_type == 'confirm' and cookie_value:
        verify_confirm_cookie(cookie_value)

    # Parse the incoming filter file.
    infilter = FilterParser.FilterParser()
    infilter.read(Defaults.FILTER_INCOMING)
    (actions,matching_line) = infilter.firstmatch(recipient_address,
                                                  sender_list,
                                                  message_body,
                                                  str(message_headers),
                                                  message_size)
    (action, option) = actions.get('incoming', (None, None))
    # Dispose of the message now if there was a filter file match.
    # Log the action along with and the matching line in the filter
    # file that caused it.
    disposal_time = time.time()
    if action in ('bounce','reject'):
        if Defaults.FILTER_BOUNCE_CC:
            send_cc(Defaults.FILTER_BOUNCE_CC)
        logit('%s (%s)' % ('BOUNCE', matching_line), disposal_time)
        bouncegen('bounce', Defaults.BOUNCE_TEXT_FILTER_INCOMING)
    elif action in ('drop','exit','stop'):
        if Defaults.FILTER_DROP_CC:
            send_cc(Defaults.FILTER_DROP_CC)
        logit('%s (%s)' % ('DROP', matching_line), disposal_time)
        mta.stop()
    elif action in ('accept','deliver','ok'):
        if option:
            msg = Deliver.Deliver(message_headers, message_body, option)
            msg.deliver()
            logit('%s %s (%s)' % ('DELIVER', msg.get_instructions()[1],
                                  matching_line), disposal_time)
            mta.stop()
        else:
            logit('%s (%s)' % ('OK', matching_line), disposal_time)
            mta.deliver(message)
    elif action == 'confirm':
        # bouncegen does logging
        bouncegen('request')

    # The message didn't match the filter file, so check if it was
    # sent to a 'tagged' address.
    # Dated tag?
    if cookie_type == 'dated' and cookie_value:
        verify_dated_cookie(cookie_value)
    # Sender tag?
    elif cookie_type == 'sender' and cookie_value:
        sender_address = globals().get('envelope_sender')
        verify_sender_cookie(sender_address,cookie_value)
    # Keyword tag?
    elif cookie_type == 'keyword' and cookie_value:
        verify_keyword_cookie(cookie_value)
        
    # If the message gets this far (i.e, was not sent to a tagged
    # address and it didn't match the filter file), then we consult
    # Defaults.ACTION_INCOMING.
    default_action = string.lower(Defaults.ACTION_INCOMING)
    if default_action in ('bounce','reject'):
        logit('%s %s' % ('BOUNCE', 'action_incoming'), disposal_time)
        bouncegen('bounce', Defaults.BOUNCE_TEXT_FILTER_INCOMING)
    elif default_action in ('drop','exit','stop'):
        logit('%s %s' % ('DROP', 'action_incoming'), disposal_time)
        mta.stop()
    elif default_action in ('accept','deliver','ok'):
        logit('%s %s' % ('OK', 'action_incoming'), disposal_time)
        mta.deliver(message)
    else:
        # bouncegen does logging
        bouncegen('request')


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