SMTPD_POLICY_README.html   [plain text]


<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<title>Postfix SMTP Access Policy Delegation </title>

<meta http-equiv="Content-Type" content="text/html; charset=us-ascii">

</head>

<body>

<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SMTP Access Policy Delegation </h1>

<hr>

<h2>Purpose of Postfix SMTP access policy delegation</h2>

<p> The Postfix SMTP server has a number of built-in mechanisms to
block or accept mail at specific SMTP protocol stages. As of version
2.1, Postfix can delegate policy decisions to an external server
that runs outside Postfix. </p>

<p> With this policy delegation mechanism, a simple <a href="#greylist">
greylist </a> policy can be implemented with only a dozen lines of
Perl, as is shown at the end of this document. Another example of
policy delegation is the SPF policy server by Meng Wong at
http://spf.pobox.com/.  Examples of both policies can be found in
the Postfix source code, in the directory examples/smtpd-policy.
</p>

<p> Policy delegation is now the preferred method for adding policies
to Postfix. It's much easier to develop a new feature in few lines
of Perl, than trying to do the same in C code. The difference in
performance will be unnoticeable except in the most demanding
environments. </p>

<p> This document covers the following topics: </p>

<ul>

<li><a href="#protocol">Policy protocol description</a>

<li><a href="#client_config">Policy client/server configuration</a>

<li><a href="#greylist">Example: greylist policy server</a>

<li><a href="#frequent">Greylisting mail from frequently forged domains</a>

<li><a href="#all_mail">Greylisting all your mail</a>

<li><a href="#maintenance">Routine greylist maintenance</a>

<li><a href="#greylist_code">Example Perl greylist server</a>

</ul>

<h2><a name="protocol">Protocol description</a></h2>

<p> The Postfix policy delegation protocol is really simple. The
client request is a sequence of name=value attributes separated by
newline, and is terminated by an empty line. The server reply is
one name=value attribute and it, too, is terminated by an empty
line. </p>

<p> Here is an example of all the attributes that the Postfix SMTP
server sends in a delegated SMTPD access policy request: </p>

<blockquote>
<pre>
request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=some.domain.tld
queue_id=8045F2AB23
sender=foo@bar.tld
recipient=bar@foo.tld
client_address=1.2.3.4
client_name=another.domain.tld
instance=123.456.7
sasl_method=plain
sasl_username=you
sasl_sender=
size=12345
[empty line]
</pre>
</blockquote>

<p> Notes: </p>

<ul>

    <li> <p> The "request" attribute is required. In this example
    the request type is "smtpd_access_policy". </p>

    <li> <p> The order of the attributes does not matter. The policy
    server should ignore any attributes that it does not care about.
    </p>

    <li> <p> When the same attribute name is sent more than once,
    the server may keep the first value or the last attribute value.
    </p>

    <li> <p> When an attribute value is unavailable, the client
    either does not send the attribute, or sends the attribute with
    an empty value ("name="). </p>

    <li> <p> An attribute name must not contain "=", null or newline,
    and an attribute value must not contain null or newline. </p>

    <li> <p> The "instance" attribute value can be used to correlate
    different requests regarding the same message delivery. </p>

</ul>

<p> The following is specific to SMTPD delegated policy requests:
</p>

<ul>

    <li> <p> Protocol names are ESMTP or SMTP. </p>

    <li> <p> Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT,
    DATA, VRFY or ETRN; these are the SMTP protocol states where
    the Postfix SMTP server makes an OK/REJECT/HOLD/etc. decision.
    </p>

    <li> <p> The SASL attributes are sent only when SASL support
    is built into Postfix. </p>

</ul>

<p> The policy server replies with any action that is allowed in a
Postfix SMTPD access(5) table. Example: </p>

<blockquote>
<pre>
action=defer_if_permit Service temporarily unavailable
[empty line]
</pre>
</blockquote>

<p> This causes the Postfix SMTP server to reject the request with
a 450 temporary error code and with text "Service temporarily
unavailable", if the Postfix SMTP server finds no reason to reject
the request permanently. </p>

<p> In case of trouble the policy server must not send a reply.
Instead the server must log a warning and disconnect.  Postfix will
retry the request at some later time.  </p>

<h2><a name="client_config">Policy client/server configuration</a></h2>

<p> The Postfix delegated policy client can connect to a TCP socket
or to a UNIX-domain socket. Examples: </p>

<blockquote>
<pre>
inet:127.0.0.1:9998
unix:/some/where/policy
unix:private/policy
</pre>
</blockquote>

<p> The first example specifies that the policy server listens on
a TCP socket at 127.0.0.1 port 9998. The second example specifies
an absolute pathname of a UNIX-domain socket. The third example
specifies a pathname relative to the Postfix queue directory; use
this for policy servers that are spawned by the Postfix master
daemon. </p>

<p> To create a policy service that listens on a UNIX-domain socket
called "policy", and that runs under control of the Postfix spawn(8)
daemon, you would use something like this: </p>

<blockquote>
<pre>
 1 /etc/postfix/master.cf:
 2     policy  unix  -       n       n       -       -       spawn
 3       user=nobody argv=/some/where/policy-server
 4 
 5 /etc/postfix/main.cf:
 6     smtpd_recipient_restrictions =
 7         ... 
 8         reject_unauth_destination 
 9         check_policy_service unix:private/policy 
10         ...
11     policy_time_limit = 3600
</pre>
</blockquote>

<p> NOTES: </p>

<ul>

<li> <p> Lines 2, 11: the Postfix spawn(8) daemon by default kills
its child process after 1000 seconds.  This is too short for a
policy daemon that may run for as long as an SMTP client is connected
to an SMTP server process. The default time limit is overruled in
main.cf with an explicit "policy_time_limit" setting.  The name of
the parameter is the name of the master.cf entry ("policy")
concatenated with the "_time_limit" suffix.  </p>

<li> <p> Lines 8, 9: always specify "check_policy_service" AFTER
"reject_unauth_destination" or else your system could become an
open relay. </p>

<li> <p> Solaris UNIX-domain sockets do not work reliably. Use
TCP sockets instead: </p>

</ul>

<blockquote>
<pre>
 1 /etc/postfix/master.cf:
 2     127.0.0.1:9998  inet  n       n       n       -       -       spawn
 3       user=nobody argv=/some/where/policy-server
 4 
 5 /etc/postfix/main.cf:
 6     smtpd_recipient_restrictions =
 7         ... 
 8         reject_unauth_destination 
 9         check_policy_service inet:127.0.0.1:9998
10         ...
11     127.0.0.1:9998_time_limit = 3600
</pre>
</blockquote>

<p> Other configuration parameters that control the client side of
the policy delegation protocol: </p>

<ul>

<li> <p> smtpd_policy_service_max_idle (default: 300s): The amount
of time before the Postfix SMTP server closes an unused policy
client connection. </p>

<li> <p> smtpd_policy_service_max_ttl (default: 1000s): The amount
of time before the Postfix SMTP server closes an active policy
client connection. </p>

<li> <p> smtpd_policy_service_timeout (default: 100s): The time
limit to connect to, send to or receive from a policy server. </p>

</ul>

<h2><a name="greylist">Example: greylist policy server</a></h2>

<p> Greylisting is a defense against junk email that is described at
http://www.greylisting.org/. The idea was discussed on the
postfix-users mailing list <a
href="http://archives.neohapsis.com/archives/postfix/2002-03/0846.html">
one year before it was popularized</a>. </p>

<p> The file examples/smtpd-policy/greylist.pl in the Postfix source
tree implements a simplified greylist policy server. This server
stores a time stamp for every (client, sender, recipient) triple.
By default, mail is not accepted until a time stamp is more than
60 seconds old. This stops junk mail with randomly selected sender
addresses, and mail that is sent through randomly selected open
proxies. It also stops junk mail from spammers that change their
IP address frequently. </p>

<p> Copy examples/smtpd-policy/greylist.pl to /usr/libexec/postfix
or whatever location is appropriate for your system. </p>

<p> In the greylist.pl Perl script you need to specify the
location of the greylist database file, and how long mail will
be delayed before it is accepted. The default settings are:
</p>

<blockquote>
<pre>
$database_name="/var/mta/greylist.db";
$greylist_delay=60;
</pre>
</blockquote>

<p> The /var/mta directory (or whatever you choose) should be
writable by "nobody", or by whatever username you configure below
in master.cf for the policy service. </p>

<p> Example: </p>

<blockquote>
<pre>
# mkdir /var/mta
# chown nobody /var/mta
</pre>
</blockquote>

<p> Note: DO NOT create the greylist database in a world-writable
directory such as /tmp or /var/tmp, and DO NOT create the greylist
database in a file system that may run out of space. Postfix can
survive "out of space" conditions with the mail queue and with the
mailbox store, but it cannot survive a corrupted greylist database.
If the file becomes corrupted you may not be able to receive mail
at all until you delete the file by hand. </p>

<p> The greylist.pl Perl script can be run under control by
the Postfix master daemon.  For example, to run the script as user
"nobody", using a UNIX-domain socket that is accessible by Postfix
processes only: </p>

<blockquote>
<pre>
1 /etc/postfix/master.cf:
2     policy  unix  -       n       n       -       -       spawn
3       user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
4 
5 /etc/postfix/main.cf:
6      policy_time_limit = 3600
</pre>
</blockquote>

<p> Notes: </p>

<ul>

<li> <p> Line 3: Specify "greylist.pl -v" for verbose logging of
each request and reply. </p>

<li> <p> Lines 2, 6: the Postfix spawn(8) daemon by default kills
its child process after 1000 seconds.  This is too short for a
policy daemon that may run for as long as an SMTP client is connected
to an SMTP server process. The default time limit is overruled in
main.cf with an explicit "policy_time_limit" setting.  The name of
the parameter is the name of the master.cf entry ("policy")
concatenated with the "_time_limit" suffix.  </p>

</ul>

<p> On Solaris you must use inet: style sockets instead of unix:
style, as detailed in the "<a href="#client_config">Policy
client/server configuration</a>" section above.  </p>

<blockquote>
<pre>
1 /etc/postfix/master.cf:
2     127.0.0.1:9998  inet  n       n       n       -       -       spawn
3       user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
4 
5 /etc/postfix/main.cf:
6      127.0.0.1:9998_time_limit = 3600
</pre>
</blockquote>

<p> To invoke this service you would specify "check_policy_service
inet:127.0.0.1:9998". </p>

<h2><a name="frequent">Greylisting mail from frequently forged domains</a></h2>

<p> It is relatively safe to turn on greylisting for specific
domains that often appear in forged email.  A list of frequently
forged MAIL FROM domains can be found at
http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in.

<blockquote>
<pre>
 1 /etc/postfix/main.cf:
 2     smtpd_recipient_restrictions =
 3         reject_unlisted_recipient
 4         ...
 5         reject_unauth_destination 
 6         check_sender_access hash:/etc/postfix/sender_access
 7         ...
 8     restriction_classes = greylist
 9     greylist = check_policy_service unix:private/policy
10 
11 /etc/postfix/sender_access:
12     aol.com     greylist
13     hotmail.com greylist
14     bigfoot.com greylist
15     ... <i>etcetera</i> ...
</pre>
</blockquote>

<p> NOTES: </p>

<ul>

<li> <p> Line 9: On Solaris you must use inet: style sockets
instead of unix: style, as detailed in the "<a href="#greylist">Example:
greylist policy server</a>" section above.  </p>

<li> <p> Line 6: Be sure to specify "check_sender_access" AFTER
"reject_unauth_destination" or else your system could become an
open mail relay. </p>

<li> <p> Line 3: With Postfix 2.0 snapshot releases,
"reject_unlisted_recipient" is called "check_recipient_maps".
Postfix 2.1 understands both forms. </p>

<li> <p> Line 3: The greylist database gets polluted quickly with
bogus addresses.  It helps if you protect greylist lookups with
other restrictions that reject unknown senders and/or recipients.
</p>

</ul>

<h2><a name="all_mail">Greylisting all your mail</a></h2>

<p> If you turn on greylisting for all mail you will almost certainly
want to make exceptions for mailing lists that use one-time sender
addresses, because such mailing lists can pollute your greylist
database relatively quickly. </p>

<blockquote>
<pre>
 1 /etc/postfix/main.cf:
 2     smtpd_recipient_restrictions =
 3         reject_unlisted_recipient
 4         ...
 5         reject_unauth_destination 
 6         check_sender_access hash:/etc/postfix/sender_access
 7         check_policy_service unix:private/policy
 8         ...
 9 
10 /etc/postfix/sender_access:
11     securityfocus.com OK
12     ...
</pre>
</blockquote>

<p> NOTES: </p>

<ul>

<li> <p> Line 7: On Solaris you must use inet: style sockets
instead of unix: style, as detailed in the "<a href="#greylist">Example:
greylist policy server</a>" section above.  </p>

<li> <p> Lines 6-7: Be sure to specify check_sender_access and
check_policy_service AFTER reject_unauth_destination or else your
system could become an open mail relay. </p>

<li> <p> Line 3: The greylist database gets polluted quickly with
bogus addresses.  It helps if you precede greylist lookups with
restrictions that reject unknown senders and/or recipients. </p>

</ul>

<h2><a name="maintenance">Routine greylist maintenance</a></h2>

<p> The greylist database grows over time, because the greylist server
never removes database entries. If left unattended, the greylist
database will eventually run your file system out of space. </p>

<p> When the status file size exceeds some threshold you can simply
rename or remove the file without adverse effects; Postfix
automatically creates a new file. In the worst case, new mail will
be delayed by an hour or so. To lessen the impact, rename or remove
the file in the middle of the night at the beginning of a weekend.
</p>

<h2><a name="greylist_code">Example Perl greylist server</a></h2>

<p> This is the Perl subroutine that implements the example greylist
policy.  It is part of a general purpose sample policy server that
is distributed with the Postfix source as
examples/smtpd-policy/greylist.pl. </p>

<pre>
#
# greylist status database and greylist time interval. DO NOT create the
# greylist status database in a world-writable directory such as /tmp
# or /var/tmp. DO NOT create the greylist database in a file system
# that can run out of space.
#
$database_name="/var/mta/greylist.db";
$greylist_delay=60;

#
# Demo SMTPD access policy routine. The result is an action just like
# it would be specified on the right-hand side of a Postfix access
# table.  Request attributes are available via the %attr hash.
#
sub smtpd_access_policy {
    my($key, $time_stamp, $now);

    # Open the database on the fly.
    open_database() unless $database_obj;

    # Lookup the time stamp for this client/sender/recipient.
    $key =
        lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
    $time_stamp = read_database($key);
    $now = time();

    # If new request, add this client/sender/recipient to the database.
    if ($time_stamp == 0) {
        $time_stamp = $now;
        update_database($key, $time_stamp);
    }

    # The result can be any action that is allowed in a Postfix access(5) map.
    #
    # To label the mail, return ``PREPEND headername: headertext''
    #
    # In case of success, return ``DUNNO'' instead of ``OK'', so that the
    # check_policy_service restriction can be followed by other restrictions.
    #
    # In case of failure, return ``DEFER_IF_PERMIT optional text...'',
    # so that mail can still be blocked by other access restrictions.
    #
    syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
    if ($now - $time_stamp &gt; $greylist_delay) {
        return "dunno";
    } else {
        return "defer_if_permit Service temporarily unavailable";
    }
}
</pre>

</body>

</html>