summaryrefslogtreecommitdiffstats
path: root/proto/FILTER_README.html
diff options
context:
space:
mode:
Diffstat (limited to 'proto/FILTER_README.html')
-rw-r--r--proto/FILTER_README.html980
1 files changed, 980 insertions, 0 deletions
diff --git a/proto/FILTER_README.html b/proto/FILTER_README.html
new file mode 100644
index 0000000..12120f0
--- /dev/null
+++ b/proto/FILTER_README.html
@@ -0,0 +1,980 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix After-Queue Content Filter </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix After-Queue Content Filter </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> This document requires Postfix version 2.1 or later. </p>
+
+<p> Normally, Postfix receives mail, stores it in the mail queue
+and then delivers it. With the external content filter described
+here, mail is filtered AFTER it is queued. This approach decouples
+mail receiving processes from mail filtering processes, and gives
+you maximal control over how many filtering processes you are
+willing to run in parallel. </p>
+
+<p> The after-queue content filter is meant to be used as follows: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local users </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <b>Content<br> filter</b> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local mailbox </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> This document describes implementations that use a single
+Postfix instance for everything: receiving, filtering and delivering
+mail. Applications that use two separate Postfix instances will
+be covered by a later version of this document. </p>
+
+<p> The after-queue content filter is not to be confused with the
+approaches described in the SMTPD_PROXY_README or MILTER_README
+documents,
+where incoming SMTP mail is filtered BEFORE it is stored into the
+Postfix queue. </p>
+
+<p> This document describes two approaches to content filter
+all email, as well as several options to filter mail selectively: </p>
+
+<ul>
+
+<li><a href="#principles">Principles of operation</a>
+
+<li>Simple content filter
+
+<ul>
+
+<li><a href="#simple_filter">Simple content filter example</a>
+
+<li><a href="#simple_performance">Simple content filter performance</a>
+
+<li><a href="#simple_limitations">Simple content filter limitations</a>
+
+<li><a href="#simple_turnoff">Turning off the simple content filter</a>
+
+</ul>
+
+<li>Advanced content filter
+
+<ul>
+
+<li><a href="#advanced_filter">Advanced content filter example</a>
+
+<li><a href="#performance">Advanced content filter performance</a>
+
+<li><a href="#advanced_turnoff">Turning off the advanced content filter</a>
+
+</ul>
+
+<li>Selective content filtering
+
+<ul>
+
+<li><a href="#remote_only">Filtering mail from outside users only</a>
+
+<li><a href="#domain_dependent">Different filters for different domains</a>
+
+<li><a href="#dynamic_filter">FILTER actions in access or header/body tables</a>
+
+</ul>
+
+</ul>
+
+
+<h2><a name="principles">Principles of operation</a> </h2>
+
+<p> An after-queue content filter receives unfiltered mail from Postfix
+(as described further below) and can do one of the following: </p>
+
+<ol>
+
+<li> <p> Re-inject the mail back into Postfix, perhaps after changing
+ content and/or destination. </p>
+
+<li> <p> Discard or quarantine the mail. </p>
+
+<li> <p> Reject the mail (by sending a suitable status code back to
+ Postfix). Postfix will send the mail back to the sender address. </p>
+
+</ol>
+
+<p> NOTE: in this time of mail worms and forged spam, it is a VERY
+BAD IDEA to send viruses back to the sender address, because the
+sender address is almost certainly not the originator. It is better
+to discard known viruses, and to quarantine material that is
+suspect so that a human can decide what to do with it. </p>
+
+<h2><a name="simple_filter">Simple content filter example</a></h2>
+
+<p> The first example is simple to set up, but has major limitations
+that will be addressed in a second example. Postfix receives
+unfiltered mail from the network with the smtpd(8) server, and
+delivers unfiltered mail to a content filter with the Postfix
+pipe(8) delivery agent. The content filter injects filtered mail
+back into Postfix with the Postfix sendmail(1) command, so that
+Postfix can deliver it to the final destination. </p>
+
+<p> This means that mail submitted via the Postfix sendmail(1)
+command cannot be content filtered. </p>
+
+<p> In the figure below, names followed by a number represent
+Postfix commands or daemon programs. See the OVERVIEW
+document for an introduction to the Postfix architecture. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td align="center" valign="top"> Unfiltered<br> <br> </td>
+
+ <td align="center" valign="top"> <tt> -&gt;</tt><br> <br> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtpd(8)<br> <br> pickup(8) </td>
+
+ <td align="center" valign="middle"> <tt> &gt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ cleanup(8) </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ qmgr(8)<br> Postfix <br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ local(8)<br> smtp(8)<br> pipe(8) </td>
+
+ <td align="center" valign="top"> <tt> -&gt;</tt><br> <tt>
+ -&gt;</tt><br> </td>
+
+ <td align="center" valign="top"> Filtered<br> Filtered<br>
+ </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="2"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td colspan="5"> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="2"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="2"> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="QSHAPE_README.html#maildrop_queue"> maildrop <br>
+ queue </a> </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
+ postdrop(1) </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
+ sendmail(1) </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Content
+ <br> filter </td>
+
+ <td colspan="2"> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The content filter can be a simple shell script like this: </p>
+
+<blockquote>
+<pre>
+ 1 #!/bin/sh
+ 2
+ 3 # Simple shell-based filter. It is meant to be invoked as follows:
+ 4 # /path/to/script -f sender recipients...
+ 5
+ 6 # Localize these. The -G option does nothing before Postfix 2.3.
+ 7 INSPECT_DIR=/var/spool/filter
+ 8 SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.
+ 9
+10 # Exit codes from &lt;sysexits.h&gt;
+11 EX_TEMPFAIL=75
+12 EX_UNAVAILABLE=69
+13
+14 # Clean up when done or when aborting.
+15 trap "rm -f in.$$" 0 1 2 3 15
+16
+17 # Start processing.
+18 cd $INSPECT_DIR || {
+19 echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }
+20
+21 cat &gt;in.$$ || {
+22 echo Cannot save mail to file; exit $EX_TEMPFAIL; }
+23
+24 # Specify your content filter here.
+25 # filter &lt;in.$$ || {
+26 # echo Message content rejected; exit $EX_UNAVAILABLE; }
+27
+28 $SENDMAIL "$@" &lt;in.$$
+29
+30 exit $?
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 8: The -G option says the filter output is not a local
+mail submission: don't do silly things like appending the local
+domain name to addresses in message headers. This option does
+nothing before Postfix version 2.3. </p>
+
+<li> <p> Line 8: The -i option says don't stop reading input when
+a line contains "." only. </p>
+
+<li> <p> Line 8: NEVER NEVER NEVER use the "-t" command-line option
+here. It will mis-deliver mail, like sending messages from a mailing
+list back to the mailing list. </p>
+
+<li> <p> Line 21: The idea is to first capture the message to
+file and then run the content through a third-party content filter
+program. </p>
+
+<li> <p> Line 22: If the message cannot be captured to file, mail
+delivery is deferred by terminating with exit status 75 (EX_TEMPFAIL).
+Postfix places the message in the deferred mail queue and tries
+again later. </p>
+
+<li> <p> Line 25: You will need to specify a real content filter
+program here that receives the content on standard input. </p>
+
+<li> <p> Line 26: If the content filter program finds a problem,
+the mail is bounced by terminating with exit status 69 (EX_UNAVAILABLE).
+Postfix will send the message back to the sender as undeliverable
+mail.
+</p>
+
+<li> <p> NOTE: in this time of mail worms and spam, it is a BAD
+IDEA to send known viruses or spam back to the sender, because that
+address is likely to be forged. It is safer to discard known viruses
+and to quarantine suspicious content so that it can
+be inspected by a human being. </p>
+
+<li> <p> Line 28: If the content is OK, it is given as input to
+the Postfix sendmail command, and the exit status of the filter
+command is whatever exit status the Postfix sendmail command
+produces. Postfix will deliver the message as usual. </p>
+
+<li> <p> Line 30: Postfix returns the exit status of the Postfix
+sendmail command. </p>
+
+</ul>
+
+<p> I suggest that you first run this script by hand until you are
+satisfied with the results. Run it with a real message (headers+body)
+as input: </p>
+
+<blockquote>
+<pre>
+% /path/to/script -f sender -- recipient... &lt;message-file
+</pre>
+</blockquote>
+
+<p> Once you're satisfied with the content filtering script: </p>
+
+<ul>
+
+<li> <p> Create a dedicated local user account called "filter". This
+user handles all potentially dangerous mail content - that is
+why it should be a separate account. Do not use "nobody", and
+most certainly do not use "root" or "postfix". </p>
+
+<li> <p> Create a directory /var/spool/filter that is accessible only
+to the "filter" user. This is where the content filtering script
+is supposed to store its temporary files. </p>
+
+<li> <p> Configure Postfix to deliver mail to the content filter
+with the pipe(8) delivery agent (see the pipe(8) manpage for a
+description of the command syntax below). </p>
+
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ filter unix - n n - 10 pipe
+ flags=Rq user=filter null_sender=
+ argv=/path/to/script -f ${sender} -- ${recipient}
+</pre>
+
+<p> This runs up to 10 content filters in parallel. Instead of a
+limit of 10 concurrent processes, use whatever process limit is
+feasible for your machine. Content inspection software can gobble
+up a lot of system resources, so you don't want to have too much
+of it running at the same time. The empty null_sender setting is
+required with Postfix 2.3 and later. </p>
+
+<li> <p> To turn on content filtering for mail arriving via SMTP
+only, append "-o content_filter=filter:dummy" to the master.cf
+entry that defines the Postfix SMTP server: </p>
+
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ smtp inet ...other stuff here, do not change... smtpd
+ -o content_filter=filter:dummy
+</pre>
+
+<p> The "-o content_filter" line causes Postfix to add one content
+filter request record to each incoming mail message, with content
+"filter:dummy". This record overrides the normal mail routing
+and causes mail to be given to the content filter instead. </p>
+
+<p> The content_filter configuration parameter expects a value of
+the form <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+master.cf; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. </p>
+
+<p> The meaning of an empty next-hop filter <i>destination</i> is
+version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $myhostname. Specify
+"default_filter_nexthop = $myhostname" for compatibility with Postfix
+2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
+</p>
+
+<p> The content_filter setting has lower precedence than a FILTER
+action that is specified in an access(5), header_checks(5) or
+body_checks(5) table. </p>
+
+<li> <p> Execute "<b>postfix reload</b>" to complete the change.
+</p>
+
+</ul>
+
+<h2> <a name="simple_performance">Simple content filter performance</a> </h2>
+
+<p> With the shell script as shown above 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 already slower than SMTP transit mail. </p>
+
+<h2><a name="simple_limitations">Simple content filter limitations</a></h2>
+
+<p> The problem with content filters like the one above is that
+they are not very robust. The reason is that 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 defined
+in the file /usr/include/sysexits.h. Instead of going to the
+deferred queue, mail will bounce. The same lack of robustness can
+happen when the content filtering software itself runs into a
+resource problem. </p>
+
+<p> The simple content filter method is not suitable for content
+filter actions that are invoked via header_checks or body_checks
+patterns. These patterns will be applied again after mail is
+re-injected with the Postfix sendmail command, resulting in a mail
+filtering loop. The advanced content filtering method (see below)
+makes it possible to turn off header_checks or body_checks patterns
+for filtered mail. </p>
+
+<h2><a name="simple_turnoff">Turning off the simple content filter</a> </h2>
+
+<p> To turn off "simple" content filtering: </p>
+
+<ul> <li> <p> Edit the master.cf file, remove the "-o
+content_filter=filter:dummy" text from the entry that defines the
+Postfix SMTP server. </p>
+
+<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
+filter request records from existing queue files. </p>
+
+<li> <p> Execute another "<b>postfix reload</b>". </p>
+
+</ul>
+
+<h2><a name="advanced_filter">Advanced content filter example</a></h2>
+
+<p> The second example is more complex, but can give better
+performance, and is less likely to bounce mail when the machine
+runs into some resource problem. This content filter receives
+unfiltered mail with SMTP on localhost port 10025, and sends filtered
+mail back into Postfix with SMTP on localhost port 10026. </p>
+
+<p> For non-SMTP capable content filtering software, Bennett Todd's
+SMTP proxy implements a nice PERL/SMTP content filtering framework.
+See: https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/. </p>
+
+<p> In the figure below, names followed by a number represent
+Postfix commands or daemon programs. See the OVERVIEW
+document for an introduction to the Postfix architecture. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td align="center" valign="middle"> Unfiltered<br> <br>
+ Unfiltered</td>
+
+ <td align="center" valign="middle"> <tt> -&gt;</tt><br> <br>
+ <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtpd(8)<br> <br> pickup(8) </td>
+
+ <td align="center" valign="middle"> <tt> &gt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ cleanup(8) </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ qmgr(8)<br> Postfix <br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtp(8)<br> <br> local(8) </td>
+
+ <td align="center" valign="middle"> <tt> -&gt;</tt><br> <br>
+ <tt> -&gt; </tt></td>
+
+ <td align="center" valign="middle"> Filtered<br> <br>
+ Filtered</td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtpd(8)<br> 10026 </td>
+
+ <td> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtp(8)<br> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td colspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle">content filter 10025</td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The example given here filters all mail, including mail that
+arrives via SMTP and mail that is locally submitted via the Postfix
+sendmail command (local submissions enter Postfix via the pickup(8)
+server; to keep the figure simple we omit local submission details).
+See examples near the end of this document for
+how to exclude local users from filtering, or how to configure a
+destination dependent content filter. </p>
+
+<p> You can expect to lose about a factor of two in Postfix
+performance for mail that arrives and leaves via SMTP, provided
+that the content filter creates no temporary files. Each temporary
+file created by the content filter adds another factor to the
+performance loss. </p>
+
+<h3>Advanced content filter: requesting that all mail is filtered</h3>
+
+<p> To enable the advanced content filter method for all mail,
+specify in main.cf: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+ receive_override_options = no_address_mappings
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The "receive_override_options" line disables address
+manipulation before the content filter, so that the content filter
+sees the original mail addresses instead of the result of virtual
+alias expansion, canonical mapping, automatic bcc, address
+masquerading, etc. </p>
+
+<li> <p> The "content_filter" line causes Postfix to add one content
+filter request record to each incoming mail message, with content
+"scan:localhost:10025". The content filter request records are
+added by the smtpd(8) and pickup(8) servers (and qmqpd(8) if you
+decide to enable this service). </p>
+
+<li> <p> Content filter requests are stored in queue files; this
+is how Postfix keeps track of what mail needs filtering. When a
+queue file contains a content filter request, the queue manager
+will deliver the mail to the specified content filter regardless
+of its final destination. </p>
+
+<li> <p> The content_filter configuration parameter expects a value
+of the form <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+master.cf; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. </p>
+
+<li> <p> The meaning of an empty next-hop filter <i>destination</i>
+is version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $myhostname. Specify
+"default_filter_nexthop = $myhostname" for compatibility with Postfix
+2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
+
+<li> <p> The content_filter setting has lower precedence than a
+FILTER action that is specified in an access(5), header_checks(5)
+or body_checks(5) table. </p>
+
+</ul>
+
+<h3> Advanced content filter: sending unfiltered mail to the content
+filter</h3>
+
+<p> In this example, "scan" is an instance of the Postfix SMTP
+client with slightly different configuration parameters. This is
+how one would set up the service in the Postfix master.cf file:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o smtp_send_xforward_command=yes
+ -o disable_mime_output_conversion=yes
+ -o smtp_generic_maps=
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> This runs up to 10 content filters in parallel. Instead
+of a limit of 10 concurrent processes, use whatever process limit
+is feasible for your machine. Content inspection software can
+gobble up a lot of system resources, so you don't want to have too
+much of it running at the same time. </p>
+
+<li> <p> With "-o smtp_send_xforward_command=yes", the scan transport
+will try to forward the original client name and IP address
+through the content filter to the
+after-filter smtpd process, so that filtered mail is logged with
+the real client name IP address. See smtp(8) and XFORWARD_README
+for more information. </p>
+
+<li> <p> The "-o disable_mime_output_conversion=yes" is a workaround
+that prevents the breaking of domainkeys and other digital signatures.
+This is needed because some SMTP-based content filters don't announce
+8BITMIME support, even though they can handle 8-bit mail. </p>
+
+<li> <p> The "-o smtp_generic_maps=" is a workaround that prevents
+local address rewriting with generic(5) maps. Such rewriting should
+happen only when mail is sent out to the Internet. </p>
+
+</ul>
+
+<h3>Advanced content filter: running the content filter</h3>
+
+<p> 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 localhost port 10025: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10025 inet n n n - 10 spawn
+ user=filter argv=/path/to/filter localhost 10026
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> "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.
+</p>
+
+<li> <p> By default, Postfix will terminate a command that runs
+longer than command_time_limit seconds (default: 1000s). This is a
+safety measure that prevents filters from running forever. </p>
+
+</ul>
+
+<p> 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, and must not use the Postfix spawn service. </p>
+
+<h3>Advanced filter: injecting mail back into Postfix</h3>
+
+<p> 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. </p>
+
+<p> 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 `.' from Postfix with `550 content
+rejected', and to disconnect without sending `.' on the connection
+that injects mail back into Postfix. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10026 inet n - n - 10 smtpd
+ -o content_filter=
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
+ -o smtpd_helo_restrictions=
+ -o smtpd_client_restrictions=
+ -o smtpd_sender_restrictions=
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ -o smtpd_relay_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> NOTE: do not use spaces around the "=" or "," characters. </p>
+
+<li> <p> NOTE: the SMTP server must not have a smaller process
+limit than the "filter" master.cf entry. </p>
+
+<li> <p> The "-o content_filter=" overrides main.cf settings, and
+requests no content filtering for mail from the content filter.
+This is required or else mail will loop. </p>
+
+<li> <p> The "-o receive_override_options" overrides main.cf settings
+to avoid duplicating work that was already done before the content
+filter. These options are complementary to the options that are
+specified in main.cf: </p>
+
+<ul>
+
+ <li> <p> We specify "no_unknown_recipient_checks" to disable
+ attempts to find out if a recipient is unknown. </p>
+
+ <li> <p> We specify "no_header_body_checks" to disable header/body
+ checks. </p>
+
+ <li> <p> We specify "no_milters" to disable Milter applications
+ (this option is available only in Postfix 2.3 and later). </p>
+
+ <li> <p> We don't specify "no_address_mappings" here. This
+ enables virtual alias expansion, canonical mappings, address
+ masquerading, and other address mappings after the content
+ filter. The main.cf setting of "receive_override_options"
+ disables these mappings before the content filter. </p>
+
+</ul>
+
+ <p> These receive override options are either implemented by the
+ SMTP server itself, or they are passed on to the cleanup server.
+ </p>
+
+<li> <p> The "-o smtpd_xxx_restrictions" and "-o mynetworks=127.0.0.0/8"
+override main.cf settings. They turn off junk mail controls that
+would only waste time here.
+
+<li> <p> With "-o smtpd_authorized_xforward_hosts=127.0.0.0/8",
+the scan transport will try to forward the original client name
+and IP address to the after-filter smtpd process, so that filtered
+mail is logged with the real client name and IP address. See
+XFORWARD_README and smtpd(8). </p>
+
+</ul>
+
+<h2><a name="performance">Advanced content filter performance</a></h2>
+
+<p> With the "sandwich" approach to content filtering described
+here, it is important to match the filter concurrency to the
+available CPU, memory and I/O resources. Too few content filter
+processes and mail accumulates in the active queue even with low
+traffic volume; too much concurrency and Postfix ends up deferring
+mail destined for the content filter because processes fail due to
+insufficient resources. </p>
+
+<p> Currently, content filter performance tuning is a process of
+trial and error; analysis is handicapped because filtered and
+unfiltered messages share the same queue. As mentioned in the
+introduction of this document, content filtering with multiple
+Postfix instances will be covered in a future version. </p>
+
+<h2><a name="advanced_turnoff">Turning off the advanced content filter</a> </h2>
+
+<p> To turn off "advanced" content filtering: </p>
+
+<ul> <li> <p> Delete or comment out the two following main.cf lines.
+The other changes made for advanced content filtering have no effect
+when content filtering is turned off. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+ receive_override_options = no_address_mappings
+</pre>
+</blockquote>
+
+<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
+filter request records from existing queue files. </p>
+
+<li> <p> Execute another "<b>postfix reload</b>". </p>
+
+</ul>
+
+<h2><a name="remote_only">Filtering mail from outside users only</a></h2>
+
+<p> The easiest approach is to configure ONE Postfix instance with
+multiple SMTP server IP addresses in master.cf: </p>
+
+<ul>
+
+<li> <p> Two SMTP server IP addresses for mail from inside users only,
+with content filtering turned off. </p>
+
+<pre>
+/etc/postfix.master.cf:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+</pre>
+
+<li> <p> One SMTP server address for mail from outside users with
+content filtering turned on. </p>
+
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o content_filter=filter-service:filter-destination
+ -o receive_override_options=no_address_mappings
+</pre>
+
+</ul>
+
+<p> After this, you can follow the same procedure as outlined in
+the "advanced" or "simple" content filtering examples above, except
+that you must not specify "content_filter" or "receive_override_options"
+in the main.cf file. </p>
+
+<h2><a name="domain_dependent">Different filters for different
+domains</a></h2>
+
+<p> If you are an MX service provider and want to apply different
+content filters for different domains, you can configure ONE Postfix
+instance with multiple SMTP server IP addresses in master.cf. Each
+address provides a different content filter service. </p>
+
+<blockquote>
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains that are filtered with service1:dest1
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o content_filter=service1:dest1
+ -o receive_override_options=no_address_mappings
+
+ # SMTP service for domains that are filtered with service2:dest2
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o content_filter=service2:dest2
+ -o receive_override_options=no_address_mappings
+</pre>
+</blockquote>
+
+<p> After this, you can follow the same procedure as outlined in
+the "advanced" or "simple" content filtering examples above, except
+that you must not specify "content_filter" or "receive_override_options"
+in the main.cf file. </p>
+
+<p> Set up MX records in the DNS that route each domain to the
+proper SMTP server instance. </p>
+
+<h2><a name="dynamic_filter">FILTER actions in access or header/body
+tables</a></h2>
+
+<p> The above filtering configurations are static. Mail that follows
+a given path is either always filtered or it is never filtered. As
+of Postfix 2.0 you can also turn on content filtering on the fly.
+</p>
+
+<p> To turn on content filtering with an access(5) table rule: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/access:
+ <i>whatever</i> FILTER foo:bar
+</pre>
+</blockquote>
+
+<p> To turn on content filtering with a header_checks(5) or
+body_checks(5) table pattern: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/header_checks:
+ /<i>whatever</i>/ FILTER foo:bar
+</pre>
+</blockquote>
+
+<p> You can do this in smtpd access maps as well as the cleanup
+server's header/body_checks. This feature must be used with great
+care: you must disable all the UCE features in the after-filter
+smtpd and cleanup daemons or else you will have a content filtering
+loop. </p>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> FILTER actions from smtpd access maps and header/body_checks
+take precedence over filters specified with the main.cf content_filter
+parameter. </p>
+
+<li> <p> If a message triggers more than one filter action, only
+the last one takes effect. </p>
+
+<li> <p> The same content filter is applied to all the recipients
+of a given message. </p>
+
+</ul>
+
+</body>
+
+</html>