This is a very first implementation of Postfix content filtering.
A Postfix content filter receives unfiltered mail from Postfix and
either bounces the mail or re-injects filtered mail back into Postfix.

It involves an incompatible change to queue file formats.  Older
Postfix versions will reject mail that needs to be content filtered,
and will move the queue file to the "corrupt" mail queue subdirectory.

This document describes two approaches to content filtering.

Simple content filtering example
================================

The first example is simpler to set up, but is also more resource
intensive.  With the shell script as shown you will lose a factor
of four in Postfix performance for transit mail that arrives and
leaves via SMTP. You will lose another factor in transit performance
for each additional temporary file that is created and deleted in
the process of content filtering.  The performance impact is less
for mail that is submitted or delivered locally, because such
deliveries are not as fast as SMTP transit mail.

The example assumes that only mail arriving via SMTP needs to be
content filtered.

      ..................................
      :            Postfix             :
   ----->smtpd \                /local---->
      :         -cleanup->queue-       :
   ---->pickup /                \smtp----->
   ^  :                        |       :
   |  :                         \pipe-----+
   |  ..................................  |
   |                                      |
   |                                      |
   +------sendmail<-------filter<---------+

1 - Create a dedicated local user account called "filter".  The
    user will never log in, and can be given a "*" password and
    non-existent shell and home directory. This user handles all
    potentially dangerous mail content - that is why it should be
    a separate account.

2 - Create a directory /var/spool/filter that is accessible only
    to the "filter" user. This is where the content filtering will
    store its temporary files.

3 - Define a content filtering entry in the Postfix master file:

    /etc/postfix/master.cf:
      filter    unix  -       n       n       -       -       pipe
        flags=Rq user=filter argv=/somewhere/filter -f ${sender} -- ${recipient}

The /some/where/filter program can be a simple shell script like this:

    #!/bin/sh

    # Localize these
    INSPECT_DIR=/var/spool/filter
    SENDMAIL="/usr/sbin/sendmail -i"

    # Exit codes from <sysexits.h>
    EX_TEMPFAIL=75
    EX_UNAVAILABLE=69

    # Clean up when done or when aborting.
    trap "rm -f in.$$" 0 1 2 3 15

    # Start processing.
    cd $INSPECT_DIR || { echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }

    cat >in.$$ || { echo Cannot save mail to file; exit $EX_TEMPFAIL; }

    # filter <in.$$ || { echo Message content rejected; exit $EX_UNAVAILABLE; }

    $SENDMAIL "$@" <in.$$

    exit $?

The idea is to first capture the message to file and then run the
content through run a third-party content filter program.  If the
mail cannot be captured to file, mail delivery is deferred by
terminating with exit status 75 (EX_TEMPFAIL).  If the content
filter program finds a problem, the mail is bounced by terminating
the shell script with exit status 69 (EX_UNAVAILABLE).  If the
content is OK, it is given as input to Postfix sendmail, and the
exit status of the filter command is whatever exit status Postfix
sendmail produces.

I suggest that you play with this script for a while until you are
satisfied with the results. Run it as the filter user, with a real
message (headers+body) as input:

    % /some/where/filter -f sender recipient... <message-file

Turn on content filtering for mail arriving via SMTP only, by
appending "-o content_filter=filter:dummy" to the master.cf
entry that defines the Postfix SMTP server:

    /etc/postfix/master.cf:
	smtp      inet     ...stuff...      smtpd
	    -o content_filter=filter:dummy

The content_filter configuration parameter accepts the same
syntax as the right-hand side in a Postfix transport table.

Simple content filter limitations
=================================

The problem with content filters like the one above is that they
are not very robust, because the software does not talk a well-defined
protocol with Postfix. If the filter shell script aborts because
the shell runs into some memory allocation problem, the script will
not produce a nice exit status as per /usr/include/sysexits.h and
mail will probably bounce. The same lack of robustness is possible
when the content filtering software itself runs into a resource
problem.

Advanced content filtering example
===================================

The second example is considerably more complex, but can give much
better performance, and is less likely to bounce mail when the
machine runs into a resource problem.  This approach uses content
filtering software that can receive and deliver mail via SMTP.
You can expect to lose about a factor of two in Postfix performance
for transit mail that arrives and leaves via SMTP, provided that
you create no temporary files. Each temporary file adds another
factor to the performance loss.

We will set up a content filtering program that receives SMTP mail
via localhost port 10025, and that submits SMTP mail back into
Postfix via localhost port 10026.

      ..................................
      :            Postfix             :
   ----->smtpd \                /local---->
      :         -cleanup->queue-       :
   ---->pickup /    ^       |   \smtp----->
      :             |       v          :
      :           smtpd    smtp        :
      :           10026     |          :
      ......................|...........
                    ^       |
                    |       v
                ....|............
                :   |     10025 :
                :   filter      :
                :               :
                .................

To enable content filtering in this manner, specify in main.cf a
new parameter:

    /etc/postfix/main.cf:
	content_filter = smtp:localhost:10025

This causes Postfix to add one extra content filtering record to
each incoming mail message, with content smtp:localhost:10025.
You can use the same syntax as in the right-hand side of a Postfix
transport table.  The content filtering records are added by the
smtpd and pickup servers.

When a queue file has content filtering information, the queue
manager will deliver the mail to the specified content filter
regardless of its final destination.

The content filter can be set up with the Postfix spawn service,
which is the Postfix equivalent of inetd. For example, to instantiate
up to 10 content filtering processes on demand:

    /etc/postfix/master.cf:
	localhost:10025     inet  n      n      n      -      10     spawn
	    user=filter argv=/some/where/filter localhost 10026

"filter" is a dedicated local user account.  The user will never
log in, and can be given a "*" password and non-existent shell and
home directory.  This user handles all potentially dangerous mail
content - that is why it should be a separate account.

In the above example, Postfix listens on port localhost:10025.  If
you want to have your filter listening on port localhost:10025
instead of Postfix, then you must run your filter as a stand-alone
program.

Note: the localhost port 10025 SMTP server filter should announce
itself as "220 localhost...", to silence warnings in the log.

The /some/where/filter command is most likely a PERL script. PERL
has modules that make talking SMTP easy. The command-line specifies
that mail should be sent back into Postfix via localhost port 10026.

For now, it is left up to the Postfix users to come up with a
PERL/SMTP framework for Postfix content filtering. If done well,
it can be used with other mailers too, which is a nice spin-off.

The simplest content filter just copies SMTP commands and data
between its inputs and outputs. If it has a problem, all it has to
do is to reply to an input of `.' with `550 content rejected', and
to disconnect without sending `.' on the connection that injects
mail back into Postfix.

The job of the content filter is to either bounce mail with a
suitable diagnostic, or to feed the mail back into Postfix through
a dedicated listener on port localhost 10026:

    /etc/postfix/master.cf:
        localhost:10026     inet  n      -      n      -      10      smtpd
	    -o content_filter= -o myhostname=localhost.domain.name

This is just another SMTP server. It is configured NOT to request
content filtering for incoming mail, has the same process limit
as the filter master.cf entry, and is configured to use a different
hostname in the greeting message (this is necessary for testing
when I simply use no filtering program and let the SMTP content
filtering interfaces talk directly to each other).
