Per user UCE control patch for Postfix

The patch is against the given Postfix snapshot/release: Older releases of the patch are available for historical reasons :-).

The most recent patch contains the following extensions, modifications:

Please note: in the examples below, long right hand side values in mapfiles are displayed in multiple lines. (After patching the source code, you can find simple examples on how to use these features in the files src/smtpd/smtpd_check.in? too.)

Extended conditional macro expansion

Postfix original conditional macro expansions were as follows:
${name?text}, $(name?text}

	Conditional expansion. If the named attribute is defined, 
	the expansion is the given text with another iteration of
	macro expansions. Otherwise, the expansion is empty.

${name:text}, $(name:text)	

	Conditional expansion. If the named attribute is undefined, 
	the expansion is the given text with another iteration of
	macro expansions. Otherwise, the expansion is empty.
The trinary (test ? if_true : if_false) conditional macro expansion was unimplemented.

This patch extends Postfix with the trinary conditional macro expansion.

However, BEWARE! BEWARE! BEWARE! INCOMPATIBILITY!

In order to use the intuitive ${name?text1:text2} or $(name?text1:text2} forms, the inverted test operator ':' has been replaced with '!'. Thus the full list of the new conditional macro expansions are:

${name?text}, $(name?text}

	Conditional expansion. If the named attribute is defined, 
	the expansion is the given text with another iteration of
	macro expansions. Otherwise, the expansion is empty.

${name?text1:text2}, $(name?text1:text2}

	Conditional expansion. If the named attribute is defined, 
	the expansion is the given text1 with another iteration of
	macro expansions. Otherwise, the expansion is the given text2
	with another iteration of macro expansions. Embedded pure ':'
	characters in text1 must be escaped as '\:'.

${name!text}, $(name!text)	

	Conditional expansion. If the named attribute is undefined, 
	the expansion is the given text with another iteration of
	macro expansions. Otherwise, the expansion is empty.

${name!text1:text2}, $(name!text1:text2}

	Conditional expansion. If the named attribute is undefined, 
	the expansion is the given text1 with another iteration of
	macro expansions. Otherwise, the expansion is the given text2
	with another iteration of macro expansions. Embedded pure ':'
	characters in text1 must be escaped as '\:'.
Please note that in the unpatched Postfix literal $name in text had to be escaped as $$name. Now for the sake of consistency, it must be escaped as \$name.

Examples:

	foo = x
	bar = y

	${foo?foo defined:foo not defined}
		foo defined
	${foo?\$foo\: "$foo":\$foo is emtpy}
		$foo: "x"
	${foo?foo defined, bar ${bar?ditto:doesn't}:foo not defined}
		foo defined, bar ditto

New macros for smtpd UCE restricion map values

In UCE restrictions, map lookups may return reject messages in the form "[45]xx text". The following new macros were implemented for the message text:
        $client_name            client hostname (or "unknown")
        $client_address         client address
        $client    		client hostname [client address]
        $helo              	announced helo name
        $sender                 sender E-mail address (sender@from.somewhere)
	$sender_name		the username part of the sender E-mail
				address (sender)
	$sender_domain		the domain part of the sender E-mail address
				(from.somewhere)
        $recipient              recipient E-mail address (recip@to.somewhere)
	$recipient_name		the username part of the recipient E-mail
				address (recip)
	$recipient_domain	the domain part of the recipient E-mail
				address (to.somewhere)
	$rbl_txt		DNS TXT lookup result for the client
				in the current RBL/RHSBL domain 
				(see example below)
        $rbl_ip                 the lookup result for the client
                                in the current RBL/RHSBL domain. 
				If the client is not in the domain, 
				it's value is "0.0.0.0".

Restriction "mapping" (user defined error messages)

Without modifying the source code, one cannot rewrite the error messages returned by the builtin Postfix restrictions. A new maptype called restrictions was implemented for rewriting the error messages:
key		any one-word Postfix restriction including user defined
		restriction classes
value		anything which is valid on the RHS in mapfiles
		(OK|RELAY|REJECT|[45]xx text|list of UCE restrictions)
The "lookup" in the map file happens as follows: Here follows a little example on how to redefine the default Postfix error message for unknown clients.
>>> from /etc/postfix/main.cf <<<
smtpd_recipient_restrictions = restrictions:/etc/postfix/unknown_client

>>> from /etc/postfix/unknown_client <<<
reject_unknown_client	450 Cannot find your hostname, [$client_address]. 
			Ask your system manager to fix your domain name registration.

Individual RBL/RHSBL lookups

In order to make possible individual RBL/RHSBL lookups, we implemented two new maptypes called rbl/rhsbl. The key/value pairs in the map files are
key		domain name
value		anything which is valid on the RHS in mapfiles
		(OK|RELAY|REJECT|[45]xx text|list of UCE restrictions)
In the case of rbl maptype: In the case of rhsbl maptype:

Example for rbl map and $rbl_txt macro usage:

>>> from /etc/postfix/main.cf <<<
smtpd_recipient_restrictions = rbl:/etc/postfix/rbl_domain

>>> from /etc/postfix/rbl_domain <<<
relays.orbs.org		554 Service unavailable; [$client_address] blocked using relays.orbs.org. 
			${rbl_txt?$rbl_txt:See http://www.orbs.org/ for details.}
Example for rhsbl map usage:
>>> from /etc/postfix/main.cf <<<
smtpd_recipient_restrictions = 
	check_sender_access rhsbl:/etc/postfix/rhsbl_sender_domain,
	rhsbl:/etc/postfix/rhsbl_recip_domain
or
>>> from /etc/postfix/main.cf <<<
smtpd_recipient_restrictions = 
	check_access \$sender rhsbl:/etc/postfix/rhsbl_sender_domain,
	check_access \$recipient rhsbl:/etc/postfix/rhsbl_recip_domain

>>> from /etc/postfix/rbl_sender_domain <<<
in.dnsbl.org		554 Service unavailable; 
			$sender_domain blocked using in.dnsbl.org. 

>>> from /etc/postfix/rbl_recip_domain <<<
in.dnsbl.org		554 Service unavailable; 
			$recipient_domain blocked using in.dnsbl.org. 
Please note, rbl/rhsbl maps don't perform DNS TXT record lookups. The $rbl_txt macro triggers the DNS TXT lookup in the current RBL/RHSBL domain. Additionally, until a new DNS A record isn't looked up by rbl/rhsbl map, $rbl_txt preserves the result of the last DNS TXT record lookup.

cidr maptype

The cidr maptype for address lookups in access tables is implemented for fun :-) and more flexibility:
key             netblock in CIDR notation (or plain IP address as 
                individual node address)
value           anything which is valid on the RHS in mapfiles
                (OK|RELAY|REJECT|[45]xx text|list of UCE restrictions)

Inline "mapfiles"

All the introduced maptypes (restrictions, rbl/rhsbl, cidr) can be used in inline mode, which (due to the parsing in main.cf) means one-liner inline mapfiles. Inline mapfiles helps to avoid multiple tiny files just for one type of lookup and it speeds up a little bit the processing. The notation is maptype:inline:variable_name. Example
>>> from /etc/postfix/main.cf <<<
rbl_domain = relays.orbs.org   554 Service unavailable; 
	[\$client_address] blocked using relays.orbs.org. 
	\${rbl_txt?\$rbl_txt:See http://www.orbs.org/ for details.}

smtpd_recipient_restrictions = rbl:inline:rbl_domain
[Don't forget to escape macro expansions like in the example above.]

DUNNO keyword

The DUNNO NOOP-valued keyword added to the possible values of restrictions. It can be useful to disable a restriction class quickly (like in the case when ORBS became unusable).

Parametrized access check

The new access check has the following syntax:
	check_access parameter maptype:mapfile
where the parameter may contain macro expressions from above.

At evaluating it, first the macros in the parameter are expanded, then the result is looked up in the specified mapfile according to the maptype.

The goal at the first version of this patch was to make UCE restrictions as configurable as possible. Let it be possible for the users to define (setup) individual restrictions based on different local spammer databases, DNS checkings, RBL-style databases.

The biggest complain we received after installing the patched system was: "But how could I let in E-mail messages from foo address despite my restriction settings?"

With the parametrized access check we can make possible for the users to set up individual exceptions against their individual UCE settings.

The example from our mail gateways:

>>> /etc/postfix/main.cf <<<
# The definition of the "atomic" restriction classes
smtpd_restriction_classes =
        class_ip,
        class_sender,
        class_dns,
        class_kfki,
        class_cern,
        class_rbl,
        class_dul,
        class_orbs,
        class_rss

class_ip = check_client_access hash:/etc/postfix/ip_address
# ...
class_rss = rbl:/etc/postfix/rbl_domain_rss

# We have per site default restriction, so the users must have
# an excplicite 'permit' at the end of their restrictions.

smtpd_recipient_restrictions =
        reject_non_fqdn_sender,
        reject_non_fqdn_recipient,
	permit_mynetworks,
	reject_unauth_destination,
	check_access \${sender}|\$recipient hash:/etc/postfix/user_exceptions,
        check_recipient_access hash:/etc/postfix/user_restrictions,
        class_ip, class_sender, class_dns, class_kfki, class_cern,
	class_rbl, class_dul, class_orbs, class_rss

>>> /etc/postfix/user_exceptions <<<
innocent1@bad.domain|one_user@our.domain		OK
innocent2@another.domain|second_user@our.domain		OK

>>> /etc/postfix/user_restrictions <<<
abuse@kfki.hu	permit
foo@kfki.hu	class_ip, class_sender, class_kfki, class_rbl, permit
bar@kfki.hu	class_ip, class_sender, class_kfki, permit

ChangeLog

26.07.2002 - 1.1.11
07.06.2002 - 1.1.10
01.03.2002 - 1.1.4
14.01.2002 - 20020107
08.01.2002 - 20020107
18.12.2001 - 20011210
08.03.2001 - 20010228
19.01.2001 - 20001217
06.11.2000 - 20001030
14.09.2000 - 20000528-2

Enjoy it!
József Kadlecsik