Exim 4 and Policyd-weight

http://blog.schalanda.name/archives/118-Exim-4-and-policyd-weight.html/

This article describes the basic steps which are necessary to use the well known policyd-weightdaemon (originally written for Postfix) with Exim 4. I tested this setup with Exim 4.66 but it should work with any Exim version higher than 4.62. Warning/Disclaimer: This setup works for me[tm]. If it doesn’t work for you, you’re on your own. You should be adequately familiar with your configuration and SMTP in general, so you won’t loose mail or break your setup.

Update: Thanks to Robert Felber for pointing out the missing $instance variable, containing a unique ID for each message at RCPT stage. Since Exim doesn’t support a corresponding expansion variable, we build our own unique ID with “$sender_host_address.$sender_address.$sender_helo_name”.

Update 2: Users of Exim versions earlier than 4.53 will need to use $message_id instead of$message_exim_id in /etc/exim/acl-check-policyd.conf.

Update 3: policyd-weight can only be interfaced using the TCP/IP socket defined with$BIND_ADDRESS and $TCP_PORT in policyd-weight.conf. Thus, this setup can only be used withExim 4.62 or higher.

First a little introduction to policyd-weight from its website:

What’s policyd-weight? policyd-weight is a Perl policy daemon for the Postfix MTA (2.1 and later) intended to eliminate forged envelope senders and HELOs (i.e. in bogus mails). It allows you to score DNSBLs (RBL/RHSBL), HELO, MAIL FROM and client IP addresses before any queuing is done. It allows you to REJECT messages which have a score higher than allowed, providing improved blocking of spam and virus mails. policyd-weight caches the most frequent client/sender combinations (SPAM as well as HAM) to reduce the number of DNS queries.

Basically these features can be implemented in Exim without using the policy server, but I think this will make a good example of how to use other policy servers written for Postfix in Exim, e. g. Postgrey or apolicy.

The installation process of policyd-weight is well described on its website, so I won’t repeat it here.

Once you have it up and running, you only need to write a small ACL for Exim. Since we need some information which is only available after a certain stage in the SMTP dialog, the best place to put policyd-weight is after the RCPT command. The corresponding ACL in Exim is acl_smtp_rcpt.

I will put the Exim configuration for policyd-weight in a seperate file to keep the main configuration file small. The path can of course differ in your setup.

The relevant code snippets in your Exim main configuration (usually /etc/exim/exim.conf) should roughly look as follows:

acl_smtp_rcpt = acl_check_rcpt
# [...]
begin acl
# [...]
acl_check_rcpt:
  .include /etc/exim/acl-check-policyd.conf

If you are not sure where to put these settings, please take look in the very well written Exim specification and read the documentation provided by your distribution.

Postfix uses a very simple protocol to connect to the policy servers. The replies of policyd-weight are also very simple and can easily be processed by Exim.

The file /etc/exim/acl-check-policyd.conf should contain the following settings:

# Send mail data to policyd-weight listening on 127.0.0.1:12525
# and save answer in $acl_m9
#
# NOTE:
# Users of Exim <4.53 need to change the queue_id:
# - queue_id=$message_exim_id\n\
# + queue_id=$message_id\n\
warn set acl_m9  = ${readsocket{inet:127.0.0.1:12525}\
                    {request=smtpd_access_policy\n\
                    protocol_state=RCPT\n\
                    protocol_name=SMTP\n\
                    helo_name=$sender_helo_name\n\
                    queue_id=$message_exim_id\n\
                    sender=$sender_address\n\
                    recipient=$local_part@$domain\n\
                    recipient_count=$rcpt_count\n\
                    client_address=$sender_host_address\n\
                    client_name=$sender_address_domain\n\
                    reverse_client_name=$sender_host_name\n\
                    instance=$sender_host_address.$sender_address.$sender_helo_name\n\n}\
                    {20s}{\n}{socket failure}}

# Defer on socket error
defer condition   = ${if eq{$acl_m9}{socket failure}{yes}{no}}
      message     = Cannot connect to policyd-weight. Please try again later.

# Set proposed action to $acl_m8 and message to $acl_m7
warn set acl_m8 = ${extract{action}{$acl_m9}}
     set acl_m7 = ${sg{$acl_m9}{\Naction=[^ ]+ (.*)\n\n\N}{\$1}}

# Write log entries for debugging purposes
# warn log_message = policyd-weight action: $acl_m8
# warn log_message = policyd-weight message: $acl_m7

# Add X-policyd-weight header line to message
warn message   = $acl_m7
     condition = ${if eq{$acl_m8}{PREPEND}{yes}{no}}

# Write log message, if policyd-weight can't run checks
warn log_message  = policyd-weight message: $acl_m7
     condition = ${if eq{$acl_m8}{DUNNO}{yes}{no}}

# Deny mails which policyd-weight thinks are spam 
deny message   = policyd-weight said: $acl_m7
     condition = ${if eq{$acl_m8}{550}{yes}{no}}

# Defer messages when policyd-weight suggests so.
defer message  = policyd-weight said: $acl_m7
      condition = ${if eq{$acl_m8}{450}{yes}{no}}

If you want to log the output of policyd-weight in your log files, just remove the hash signs in front of the lines with log_message. Of course you can test the effectiveness of policyd-weight for first and only log the result. Just comment out the last three ACL verbs (warn, deny and defer; the last 11 lines) and their conditions.

Please be aware that this ACL refuses to receive mails, when policyd-weight says so. Therefore the settings in your policyd-weight.conf should be reasonable liberal, so you do not loose legitimate messages.

The Exim configuration is very flexible and powerful, so policyd-weight could be invoked on a per-domain or per-user basis. The mails could also be tagged based on the results of policyd-weight. Basically there are no limits but your imagination. Just take a look in the already mentioned Exim specification.