POLICY DELEGATION PROTOCOL
==========================

The Postfix SMTP server has a number of built-in mechanisms to
block or accept mail at specific SMTP protocol stages. Optionally,
it can be configured to delegate policy decisions to an external
server that runs outside Postfix. A simple greylist policy can be
implemented with only a dozen lines of PERL.

This document describes the following:

- The Postfix policy delegation protocol.

- Using the example greylist policy server.

PROTOCOL DESCRIPTION
====================

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.

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

    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
    [empty line]

- The "request" attribute is required. In this example the request
  type is "smtpd_access_policy".
- The order of the attributes does not matter. The policy server
  should ignore any attributes that it does not care about.
- When the same attribute name is sent more than once, the server
  may keep the first value or the last attribute value.
- An attribute name must not contain "=", null or newline, and an
  attribute value must not contain null or newline.

The following is specific to SMTPD delegated policy requests:

- Protocol names are ESMTP or SMTP.
- 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.

The policy server replies with any action that is allowed in a
Postfix SMTPD access table. Example:

    action=450 Service temporarily unavailable
    [empty line]

This causes Postfix to reject the request with a 450 temporary
error code and with text "Service temporarily unavailable".

In case of trouble the policy server must log a warning and
disconnect.  Postfix will retry the request at some later time.

CLIENT SIDE CONFIGURATION
=========================

The delegated policy client can connect to a TCP socket or to a
UNIX-domain socket. Examples:

    inet:localhost:9998
    unix:/some/where/policy
    unix:private/policy

The first example specifies that the policy server listens on
localhost 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.

To use the delegated policy service for SMTPD access control,
specify "check_policy_service" anywhere in the list of
smtpd_recipient_restrictions:

/etc/postfix/main.cf:
    smtpd_recipient_restrictions =
        ... 
        reject_unauth_destination 
        check_policy_service unix:private/policy 
        ...
    policy_time_limit = 3600

NOTE: specify "check_policy_service" AFTER "reject_unauth_destination"
or else your system could become an open relay.

NOTE: Postfix by default kills a command 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 with an explicit policy_time_limit setting.

NOTE: Solaris UNIX-domain sockets do not work reliably. Use TCP
sockets instead:

/etc/postfix/main.cf:
    smtpd_recipient_restrictions =
        ... 
        reject_unauth_destination 
        check_policy_service inet:localhost:9998
        ...
    localhost:9998_time_limit = 3600

Other client-side configuration parmeters:

- smtpd_policy_service_max_idle (default: 300s): the amount of time
  before the Postfix SMTP server closes an unused policy client
  connection.  The socket is closed while the SMTP server waits
  for new a SMTP client.

- smtpd_policy_service_max_ttl (default: 1000s): the amount of time
  before the Postfix SMTP server closes an active policy client
  connection. The socket is closed while the SMTP server waits for
  new a SMTP client.

- smtpd_policy_service_timeout (default: 100s): the time limit to
  connect to, send to or receive from a policy server.

EXAMPLE: GREYLIST POLICY SERVER
===============================

The file examples/smtpd-policy/smtpd-policy.pl in the Postfix source
tree implements an example 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.

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

In the smtpd-policy.pl PERL script you need to specify the location
of the greylist database file. The default location is:

    $database_name="/var/mta/smtpd-policy.db";

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.

Example:

    # mkdir /var/mta
    # chown nobody /var/mta

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 can run out of space easily. If the
file becomes corrupted you will not be able to receive mail until
you delete the file by hand.

The smtpd-policy.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:

/etc/postfix/master.cf:
    policy  unix  -       n       n       -       -       spawn
      user=nobody argv=/usr/bin/perl /usr/libexec/postfix/smtpd-policy.pl

Specify "...postfix/smtpd-policy.pl -v" for verbose logging of each
request and reply.

Greylisting mail from frequently forged domains
-----------------------------------------------

It is relatively safe to turn on greylisting for specific domains
that often appear in forged email. However, sites like AOL may
blacklist you when they find that you are probing them too often,
or when you're probing them too often for non-existent addresses.

/etc/postfix/main.cf:
    smtpd_recipient_restrictions =
	reject_unlisted_recipient
        ...
        reject_unauth_destination 
        check_sender_access hash:/etc/postfix/sender_access
        ...
    restriction_classes = greylist
    greylist = check_policy_service unix:private/policy
    policy_time_limit = 3600

/etc/postfix/sender_access:
    aol.com     greylist
    hotmail.com greylist
    bigfoot.com greylist
    ... etcetera ...

Be sure to specify check_sender_access AFTER reject_unauth_destination
or else your system could become an open mail relay.

The greylist database gets polluted quickly with bogus addresses.
It can help if you protect greylist lookups with restrictions that
reject unknown senders and/or recipients.

A list of frequently forged MAIL FROM domains can be found at
http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in

Greylisting all your mail
-------------------------

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.

/etc/postfix/main.cf:
    smtpd_recipient_restrictions =
	reject_unlisted_recipient
        ...
        reject_unauth_destination 
        check_sender_access hash:/etc/postfix/sender_access
        check_policy_service unix:private/policy
        ...
    policy_time_limit = 3600

/etc/postfix/sender_access:
    securityfocus.com OK
    ...

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.

The greylist database gets polluted quickly with bogus addresses.
It can help if you protect greylist lookups with restrictions that
reject unknown senders and/or recipients.

ROUTINE MAINTENANCE
===================

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.

When the status file size exceeds some threshold you can simply
rename or remove the file without adverse effects. 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.

SAMPLE POLICY ROUTINE
=====================

This is the PERL subroutine that implements the example greylist policy.

#
# 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/smtpd-policy.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);
    }

    # 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, specify DEFER_IF_PERMIT 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 > $greylist_delay) {
        return "dunno";
    } else {
        return "defer_if_permit Service temporarily unavailable";
    }
}
