[Exim] Python MIME filter script updated to support signatur…

Top Page
Delete this message
Reply to this message
Author: Paul Sheer
Date:  
To: exim-users
Subject: [Exim] Python MIME filter script updated to support signature inclusion and virus filtering

-paul

#!/usr/bin/python

print_signature = 1
filter_viruses = 1

# Usage:
#    exim-filter-mime.py <mime-type> ...
#
# Example:
#    exim-filter-mime.py audio image/jpg video
#
# This script reads from stdin and writes to stdout.
# It strips all the mime attachments from a mail message
# that are one of the mime types listed on the command line
#
# Exim can use it in its configuration file, for example, as follows:
#
#    remote_smtp:
#      driver = smtp
#      .
#      .
#      .
#      transport_filter = /etc/exim-filter-mime.py audio video image
#
#
#  Note that the files /etc/exim-signature.html and /etc/exim-signature
#  must exist and be world readable.


have_written_signature = 0
have_written_html_signature = 0
attachment_count = 0

# The attachment is replaced with the following:
def cheeky_response (part):
    print "[File `%s' of type `%s' is meant to go here]" % (part.getheader('Content-Description'), string.lower (part.gettype ()))
    print
    print "This host is restricted from transmitting %s files and" % (string.lower (part.getmaintype ()),)
    print "hence this attachment was stripped from the mail message."
    print


def cheeky_viruswarning (part):
    print "[File `%s' is meant to go here]" % (part.getheader('Content-Description'))
    print
    print "This host is restricted from transmitting .vbs, .exe, .bat and other files"
    print "due to potential virus hazards, and hence this attachment was stripped from"
    print "the mail message."
    print


def write_signature():
    sys.stdout.write (open ("/etc/exim-signature").read())
    sys.stdout.write ("\n");


def write_html_signature():
    sys.stdout.write (open ("/etc/exim-signature.html").read())
    sys.stdout.write ("\n");


def write_header(s):
    if len (s) > 256:
    s = s[:255] + "\n"
    sys.stdout.write (s)


def process_part (part, boundaries, keepheader):
    global attachment_count
    global have_written_signature
    global have_written_html_signature
    l = None
    not_allowable_mimetype = 0
    not_allowable_filetype = 0


    if string.lower (part.getmaintype ()) in sys.argv[1:] or string.lower (part.gettype ()) in sys.argv[1:]:
    not_allowable_mimetype = 1


    if filter_viruses:
# perlre(1) says that `(?>pattern)' is the same as `(?=(pattern))\1'
# Otherwise, these are taken from the regular expressions in http://www.exim.org/system_filter.exim
    if re.search ( \
        'name=("[^"]+\\.(?:vb[se]|ws[fh]|jse?|exe|com|shs|bat)"|[\\w.-]+\\.(?:vb[se]|ws[fh]|jse?|exe|com|shs|bat))' \
        , string.join (part.getplist ())):
        not_allowable_filetype = 1
    if re.search ( \
        '(?:Content-(?:Type:(?=(\\s*))\\1[\\w-]+/[\\w-]+|Disposition:(?=(\\s*))\\1attachment);(?=(\\s*))\\1(?:file)?name=|begin(?=(\\s+))\\1[0-7]{3,4}(?=(\\s+))\\1)("[^"]+\\.(?:vb[se]|ws[fh]|jse?|exe|com|shs|bat)"|[\\w.-]+\\.(?:vb[se]|ws[fh]|jse?|exe|com|shs|bat))[\\s;]' \
        , string.join (part.headers)):
        not_allowable_filetype = 1


    if not_allowable_filetype or not_allowable_mimetype:


# Generate a new header
    if keepheader:
        for l in message.headers:
        if not re.match ("^Content-[^:]*: ", l):
            write_header (l)
    print "Content-Type: TEXT/plain; charset=us-ascii"
    print


# Then give a cheeky message of our own:
    if not_allowable_mimetype:
        cheeky_response (part)
    else:
        cheeky_viruswarning (part)


# Skip through the actually message body:
    l = " "
    while not l in boundaries:
        l = part.fp.readline ()


# write the boundary:
    sys.stdout.write (l)


    else:
# if it is anything else (like text/plain, application/octet-stream etc.
# then write the header...
    p = ""
    for p in part.headers:
        write_header (p)
    print
# ... and then write the body
    l = " "
    attachment_count = attachment_count + 1
    while not l in boundaries:
        l = part.fp.readline ()
# now add a signature onto the first html or plain text part of the mail...
        if print_signature:
        if l in boundaries:
            if not have_written_signature:
            if string.lower (part.gettype ()) == "text/plain":
                have_written_signature = 1
                write_signature ();
            if not have_written_html_signature:
            if string.lower (part.gettype ()) == "text/html":
                have_written_html_signature = 1
                write_html_signature ();
        if l:
        sys.stdout.write (l)
#...but not after first two attachments.
    if attachment_count >= 2:
        have_written_html_signature = 1
        have_written_signature = 1
    return l



import sys, mimetools, re, string

message = mimetools.Message (sys.stdin, 0)

# Not a multipart message, so just dump the whole thing:
if message.getmaintype () != "multipart":
    process_part (message, [None, ""], 1)
    if print_signature:
    write_signature ()
    sys.exit (0)


# Print out the header:
for l in message.headers:
    write_header (l)
print


# Mime boundaries:
boundaries = [None, "", "--" + message.getparam ("boundary") + "\n", "--" + message.getparam ("boundary") + "--\n"]

# Read and write the initial part of the message up to the
# first boundary. This will usually explain that this is a
# MIME message:
l = " "
while not l in boundaries:
    l = message.fp.readline ()
    sys.stdout.write (l)


while l and l != boundaries[-1]:
    part = mimetools.Message (message.fp, 0)
    l = process_part (part, boundaries, 0)


# write any trailing stuff
sys.stdout.write (message.fp.read())