postfwd is written to combine complex postfix restrictions in a ruleset similar to those of the most firewalls.
The program uses the postfix policy delegation protocol to control access to the mail system before a message
has been accepted (please visit http://www.postfix.org/SMTPD_POLICY_README.html for more information).

postfwd allows you to choose an action (e.g. reject, dunno) for a combination of several smtp parameters
(like sender and recipient address, size or the client's TLS fingerprint). Also it offers simple macros/acls
which should allow straightforward and easy-to-read configurations.

You may use these identifiers as target for the `jump()` command (see ACTIONS section below). Leading
or trailing whitespace characters will be ignored. Use '#' to comment your configuration. Others will
appreciate.

A ruleset consists of one or multiple rules, which can be loaded from files or passed as command line
arguments. Please see the COMMAND LINE section below for more information on this topic.

rblcount, rhsblcount - minimum RBL/RHSBL hitcounts to match. if not specified
a single RBL/RHSBL hit will match the rbl/rhsbl items.
you may specify 'all' to evaluate all items, and use
it as variable in an action (see ACTIONS section)
(default: 1)

sender_localpart, - the local-/domainpart of the sender address
sender_domain

recipient_localpart, - the local-/domainpart of the recipient address
recipient_domain

helo_address - postfwd tries to look up the helo_name. use
helo_address=!!(0.0.0.0/0) to check for unknown.
Please do not use this for positive access control
(whitelisting), as it might be forged.

sender_ns_names, - postfwd tries to look up the names/ip addresses
sender_ns_addrs of the nameservers for the sender domain part.
Please do not use this for positive access control
(whitelisting), as it might be forged.

sender_mx_names, - postfwd tries to look up the names/ip addresses
sender_mx_addrs of the mx records for the sender domain part.
Please do not use this for positive access control
(whitelisting), as it might be forged.

version - postfwd version, contains "postfwd n.nn"
this enables version based checks in your rulesets
(e.g. for migration). works with old versions too,
because a non-existing item always returns false:
# version >= 1.10
id=R01; version~=1\.[1-9][0-9]; sender_domain==some.org \
; action=REJECT sorry no access

the current list can be found at http://www.postfix.org/SMTPD_POLICY_README.html. Please read carefully about which
attribute can be used at which level of the smtp transaction (e.g. size will only work reliably at END-OF-MESSAGE level).
Pattern matching is performed case insensitive.

Multiple use of the same item is allowed and will compared as logical OR, which means that this will work as expected:

This is only valid for PCRE values (see list above). The comparison will be performed as case insensitive exact match.
Use the '-vv' option to debug.

These special items will be reset for any new rule:

rblcount - contains the number of RBL answers
rhsblcount - contains the number of RHSBL answers
matches - contains the number of matched items
dnsbltext - contains the dns TXT part of all RBL and RHSBL replies in the form
rbltype:rblname:<txt>; rbltype:rblname:<txt>; ...

These special items will be changed for any matching rule:

request_hits - contains ids of all matching rules

This means that it might be necessary to save them, if you plan to use these values in later rules:

This will check the modification time of /etc/postfwd/client_whitelist every time the rule is evaluated and reload it as
necessary. Of course this might increase the system load, so please use it with care.

Actions will be replied to postfix as result to policy delegation requests. Any action that postfix understands is allowed - see
``man 5 access'' or http://www.postfix.org/access.5.html for a description. If no action is specified, the postfix WARN action
which simply logs the event will be used for the corresponding rule.

postfwd will return dunno if it has reached the end of the ruleset and no rule has matched. This can be changed by placing a last
rule containing only an action statement:

postfwd actions control the behaviour of the program. Currently you can specify the following:

jump (<id>)
jumps to rule with id <id>, use this to skip certain rules.
you can jump backwards - but remember that there is no loop
detection at the moment! jumps to non-existing ids will be skipped.

score (<score>)
the request's score will be modified by the specified <score>,
which must be a floating point value. the modificator can be either
+n.nn adds n.nn to current score
-n.nn sustracts n.nn from the current score
*n.nn multiplies the current score by n.nn
/n.nn divides the current score through n.nn
=n.nn sets the current score to n.nn
if the score exceeds the maximum set by `--scores` option (see
COMMAND LINE) or the score item (see ITEMS section), the action
defined for this case will be returned (default: 5.0=>"REJECT postfwd score exceeded").

set (<item>=<value>,<item>=<value>,...)
this command allows you to insert or override request attributes, which then may be
compared to your further ruleset. use this to speed up repeated comparisons to large item lists.
please see the EXAMPLES section for more information. you may separate multiple key=value pairs
by "," characters.

rate (<item>/<max>/<time>/<action>)
this command creates a counter for the given <item>, which will be increased any time a request
containing it arrives. if it exceeds <max> within <time> seconds it will return <action> to postfix.
rate counters are very fast as they are executed before the ruleset is parsed.
please note that <action> is currently limited to postfix actions (no postfwd actions)!
# no more than 3 requests per 5 minutes
# from the same "unknown" client
id=RATE01 ; client_name==unknown ; \
action=rate(client_address/3/300/450 4.7.1 sorry, max 3 requests per 5 minutes)
Please note also that the order of rate limits in your ruleset is important, which means
that this:
# works as expected
id=R001; action=rcpt(sender/500/3600/REJECT limit of 500 recipients per hour for sender $$sender exceeded)
id=R002; action=rcpt(sender/200/3600/WARN state YELLOW for sender $$sender)
leads to different results than this:
# rule R002 never gets executed
id=R001; action=rcpt(sender/200/3600/WARN state YELLOW for sender $$sender)
id=R002; action=rcpt(sender/500/3600/REJECT limit of 500 recipients per hour for sender $$sender exceeded)

size (<item>/<max>/<time>/<action>)
this command works similar to the rate() command with the difference, that the rate counter is
increased by the request's size attribute. to do this reliably you should call postfwd from
smtpd_end_of_data_restrictions. if you want to be sure, you could check it within the ruleset:
# size limit 1.5mb per hour per client
id=SIZE01 ; protocol_state==END-OF-MESSAGE ; client_address!=10.1.1.1; \
action=size(client_address/1572864/3600/450 4.7.1 sorry, max 1.5mb per hour)

rcpt (<item>/<max>/<time>/<action>)
this command works similar to the rate() command with the difference, that the rate counter is
increased by the request's recipient_count attribute. to do this reliably you should call postfwd
from smtpd_data_restrictions or smtpd_end_of_data_restrictions. if you want to be sure, you could
check it within the ruleset:
# recipient count limit 3 per hour per client
id=RCPT01 ; protocol_state==END-OF-MESSAGE ; client_address!=10.1.1.1; \
action=rcpt(client_address/3/3600/450 4.7.1 sorry, max 3 recipients per hour)

ask (<addr>:<port>[:<ignore>])
allows to delegate the policy decision to another policy service (e.g. postgrey). the first
and the second argument (address and port) are mandatory. a third optional argument may be
specified to tell postfwd to ignore certain answers and go on parsing the ruleset:
# example1: query postgrey and return it's answer to postfix
id=GREY; client_address==10.1.1.1; action=ask(127.0.0.1:10031)
# example2: query postgrey but ignore the answer, if it matches 'DUNNO'
# and continue parsing postfwd's ruleset
id=GREY; client_address==10.1.1.1; action=ask(127.0.0.1:10031:^dunno$)

wait (<delay>)
pauses the program execution for <delay> seconds. use this for
delaying or throtteling connections.

note (<string>)
just logs the given string and continues parsing the ruleset.
if the string is empty, nothing will be logged (noop).

quit (<code>)
terminates the program with the given exit-code. postfix doesn`t
like that too much, so use it with care.

The plugin interface allow you to define your own checks and enhance postfwd's
functionality. Feel free to share useful things!

Warning

Note that the plugin interface is still at devel stage. Please test your plugins
carefully, because errors may cause postfwd to break! It is also
allowed to override attributes or built-in functions, but be sure that you know
what you do because some of them are used internally.

Please keep security in mind, when you access sensible ressources and never, ever
run postfwd as privileged user! Also never trust your input (especially hostnames,
and e-mail addresses).

ITEMS

Item plugins are perl subroutines which integrate additional attributes to requests
before they are evaluated against postfwd's ruleset like any other item of the
policy delegation protocol. This allows you to create your own checks.

plugin-items can not be used selective. these functions will be executed for every
request postfwd receives, so keep performance in mind.

SYNOPSIS: %result = postfwd_items_plugin{<name>}(%request)

means that your subroutine, called <name>, has access to a hash called %request,
which contains all request attributes, like $request{client_name} and must
return a value in the following form:

save: $result{<item>} = <value>

this creates the new item <item> containing <value>, which will be integrated in
the policy delegation request and therefore may be used in postfwd's ruleset.

--proto <type>
The protocol type for postfwd's socket. Currently you may use 'tcp' or 'unix' here.
To use postfwd with a unix domain socket, run it as follows:
postfwd --proto=unix --port=/somewhere/postfwd.socket

-u, --user <name>
Changes real and effective user to <name>.

-g, --group <name>
Changes real and effective group to <name>.

--umask <mask>
Changes the umask for filepermissions (unix domain sockets, pidfiles).
Attention: This is umask, not chmod - you have to specify the bits that
should NOT apply. E.g.: umask 077 equals to chmod 700.

-R, --chroot <path>
Chroot the process to the specified path.
Test this before using - you might need some libs there.

--pidfile <path>
The process id will be saved in the specified file.

--facility <f>
sets the syslog facility, default is 'mail'

--socktype <s>
sets the Sys::Syslog socktype to 'native', 'inet' or 'unix'.
Default is to auto-detect this depening on module version and os.

--plugins <file>
Loads postfwd plugins from file. Please see http://postfwd.org/postfwd.plugins
or the plugins.postfwd.sample that is available from the tarball for more info.

Optional arguments

These parameters influence the way postfwd is working. Any of them can be combined.

-v, --verbose
Verbose logging displays a lot of useful information but can cause
your logfiles to grow noticeably. So use it with caution. Set the option
twice (-vv) to get more information (logs all request attributes).

-c, --cache <int> (default=600)
Timeout for request cache, results for identical requests will be
cached until config is reloaded or this time (in seconds) expired.
A setting of 0 disables this feature.

--cache-no-size
Ignores size attribute for cache comparisons which will lead to better
cache-hit rates. You should set this option, if you don't use the size
item in your ruleset.

--cache-no-sender
Ignores sender address for cache comparisons which will lead to better
cache-hit rates. You should set this option, if you don't use the sender
item in your ruleset.

--cache-rdomain-only
This will strip the localpart of the recipient's address before filling the
cache. This may considerably increase cache-hit rates.

--cache-rbl-timeout <timeout> (default=3600)
This default value will be used as timeout in seconds for rbl cache items,
if not specified in the ruleset.

--cache-rbl-default <pattern> (default=^127\.0\.0\.\d+$)
Matches <pattern> to rbl/rhsbl answers (regexp) if not specified in the ruleset.

--cacheid <item>, <item>, ...
This csv-separated list of request attributes will be used to construct
the request cache identifier. Use this only, if you know exactly what you
are doing. If you, for example, use postfwd only for RBL/RHSBL control,
you may set this to
postfwd --cache=3600 --cacheid=client_name,client_address
This increases efficiency of caching and improves postfwd's performance.
Warning: You should list all items here, which are used in your ruleset!

--cleanup-requests <interval> (default=600)
The request cache will be searched for timed out items after this <interval> in
seconds. It is a minimum value. The cleanup process will only take place, when
a new request arrives.

--cleanup-rbls <interval> (default=600)
The rbl cache will be searched for timed out items after this <interval> in
seconds. It is a minimum value. The cleanup process will only take place, when
a new request arrives.

--cleanup-rates <interval> (default=600)
The rate cache will be searched for timed out items after this <interval> in
seconds. It is a minimum value. The cleanup process will only take place, when
a new request arrives.

-S, --summary <int> (default=600)
Shows some usage statistics (program uptime, request counter, matching rules)
every <int> seconds. This option is included by the -v switch.
This feature uses the alarm signal, so you can force postfwd to dump the stats
using `kill -ALRM <pid>` (where <pid> is the process id of postfwd).

--no-rulestats
Disables per rule statistics. Keeps your log clean, if you do not use them.
This option has no effect without --summary or --verbose set.

-L, --stdoutlog
Redirects all syslog messages to stdout for debugging. Never use this with postfix!

-t, --test
In test mode postfwd always returns "dunno", but logs according
to it`s ruleset. -v will be set automatically with this option.

-n, --nodns
Disables all DNS based checks like RBL checks. Rules containing
such elements will be ignored.

-n, --nodnslog
Disables logging of dns events.

--dns_timeout (default: 14)
Sets the timeout for asynchonous dns queries in seconds. This value will apply to
all dns items in a rule.

--dns_timeout_max (default: 10)
Sets the maximum timeout counter for dnsbl lookups. If the timeouts exceed this value
the corresponding dnsbl will be deactivated for a while (see --dns_timeout_interval).

--dns_timeout_interval (default=1200)
The dnsbl timeout counter will be cleaned after this interval in seconds. Use this
in conjunction with the --dns_timeout_max parameter.

--dns_async_txt
Perform dnsbl A and TXT lookups simultaneously (otherwise only for listings with at
least one A record). This needs more network bandwidth due to increased queries but
might increase throughput because the lookups can be parallelized.

--dns_max_ns_lookups (default=0)
maximum ns names to lookup up with sender_ns_addrs item. use 0 for no maximum.

--dns_max_mx_lookups (default=0)
maximum mx names to lookup up with sender_mx_addrs item. use 0 for no maximum.

-I, --instantcfg
The config files, specified by -f will be re-read for every request
postfwd receives. This enables on-the-fly configuration changes
without restarting. Though files will be read only if necessary
(which means their access times changed since last read) this might
significantly increase system load.

--keep_rates (default=0)
With this option set postfwd does not clear the rate limit counters on reload. Please
note that you have to restart (not reload) postfwd with this option if you change
any rate limit rules.

--config_timeout (default=3)
timeout in seconds to parse a single configuration line. if exceeded, the rule will
be skipped. this is used to prevent problems due to large files or loops.

Informational arguments

These arguments are for command line usage only. Never ever use them with postfix spawn!

-C, --showconfig
Displays the current ruleset. Use -v for verbose output.

-P, --perfmon
This option turns of any syslogging and output. It is included
for performance testing.

The postfwd ruleset can be specified at the commandline (-r option) or be read from files (-f). The order of your arguments will be kept. You should
check the parser with the -C | --showconfig switch at the command line before applying a new config. The following call:

When a policy delegation request arrives it will be compared against postfwd`s ruleset. To inspect the processing in detail you should increase
verbority using use the ``-v'' or ``-vv'' switch. ``-L'' redirects log messages to stdout.

Keeping the order of the ruleset in general, items will be compared in random order, which basically means that

A rule hits when all items (or at least one element of a list for each item) have matched. As soon as one item (or all elements of a list) fails
to compare against the request attribute the parser will jump to the next rule in the postfwd ruleset.

If a rule matches, there are two options:

* Rule returns postfix action (dunno, reject, ...)
The parser stops rule processing and returns the action to postfix. Other rules will not be evaluated.

* Rule returns postfwd action (jump(), note(), ...)
The parser evaluates the given action and continues with the next rule (except for the jump() or quit() actions - please see the ACTIONS section
for more information). Nothing will be sent to postfix.

If no rule has matched and the end of the ruleset is reached postfwd will return dunno without logging anything unless in verbose mode. You may
simply place a last `catch-all´ rule to change that behaviour:

... <your rules> ...
id=DEFAULT ; action=dunno

will log any request that passes the ruleset without having hit a prior rule.

The common way to use postfwd is to start it as daemon, listening at a specified tcp port.
As postfwd will run in a single instance (multiplexing mode), it will take most benefit of
it`s internal caching in that case. Start postfwd with the following parameters:

If you want to check for size or rcpt_count items you must integrate postfwd in smtp_data_restrictions or
smtpd_end_of_data_restrictions. Of course you can also specify a restriction class and use it in your access
tables. First create a file /etc/postfix/policy containing:

domain1.local postfwdcheck
domain2.local postfwdcheck
...

Then postmap that file (`postmap hash:/etc/postfix/policy`), open your main.cf and enter

There might be several reasons for you to use postfwd via a tcp wrapper package like xinetd (see http://www.xinetd.org/).
I won`t discuss that here. If you plan to do so, just add the following line to your /etc/services file:

postfwd is free software and released under BSD license, which basically means
that you can do what you want as long as you keep the copyright notice:

Copyright (c) 2007, Jan Peter Kessler
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of the authors nor the names of his contributors
may be used to endorse or promote products derived from this
software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY ME ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.