Amavisd-new, ClamAV and SpamAssassin

Nota bene:

/!\ Notice: This documentation was written for CentOS 5. It may not be accurate for CentOS 6 or subsequent releases.

1. Introduction

Amavisd-new is a reliable high-performance interface between an email server (MTA) and content checkers such as virus scanners (ClamAV), and/or SpamAssassin. Amavisd-new supports both (E)SMTP and LMTP protocols as well as UNIX sockets for communicating with the MTA and content checkers. In addition, it may also use dedicated helper programs such as the Mail::SpamAssassin Perl module.

Amavisd-new supports a number of MTA's. As the Amavisd-new documentation states, Amavisd-new works "best with Postfix, fine with dual-sendmail setup and Exim v4, works with sendmail/milter, or with any MTA as a SMTP relay". This guide was written and tested on Postfix and can be used to compliment the basic Postfix guide here. Other MTA's may get added later.

We are going to configure Amavisd-new's daemon, amavisd, to accept mail from our MTA, pass it to ClamAV and SpamAssassin for checking, and then return it back to our MTA for delivery. Amavisd will use lmtp listening on TCP port 10024 to accept mail from our MTA and then pass it to ClamAV using a local UNIX socket and SpamAssassin using the Mail::SpamAssassin Perl module. Scanned mail will then be returned to our MTA using smtp on TCP port 10025 for delivery.

Amavisd-new doesn't have to reside on the same physical server as the MTA, and in high load environments it is not uncommon to have Amavisd-new, ClamAV and SpamAssassin on a physically separate server than the MTA.

2. Installation

Amavisd-new and ClamAV were installed from the RPMForge repository. To enable the RPMForge repository, please see the RPMForge instructions.

SpamAssassin is part of the CentOS base repository, but RPMForge carries a more current version of it. You should consider using the version from RPMForge. To do so (especially if you use the priorities plugin for yum), add the following to the [base] and the [updates] sections of your /etc/yum.repos.d/CentOS-Base.repo file:

[base]
exclude=spamassass*
...
[updates]
exclude=spamassass*
...

First, install amavisd-new, clamav and spamassassin packages:

yum --enablerepo=rpmforge,rpmforge-extras install amavisd-new clamav clamav-devel clamd spamassassin

This will likely also install a bunch of dependencies including various perl modules and archive packages. If all went well, two new users, amavis and clamav should have been installed onto the system:

# cat /etc/passwd | grep "amavis\|clamav"
clamav:x:101:102:Clam Anti Virus Checker:/var/clamav:/sbin/nologin
amavis:x:102:103:Amavis email scan user:/var/amavis:/bin/sh

In addition, the clamav user should automatically have been added to the amavis group:

# groups clamav
clamav : clamav amavis

If not, you can manually add clamav to the amavis group:

gpasswd -a clamav amavis

Finally, three new services should have been added to the system

# chkconfig --list | grep "amavisd\|clamd\|spamassassin"
amavisd         0:off   1:off   2:on    3:on    4:on    5:on    6:off
clamd           0:off   1:off   2:on    3:on    4:on    5:on    6:off
spamassassin    0:off   1:off   2:off   3:off   4:off   5:off   6:off

The spamassassin service, which starts spamd, can be set to off as Amavisd-new doesn't actually use the spamassassin daemon (spamd) but rather loads spamassassin as a module.

3. Configuration

SpamAssassin actually requires no special configuration to work with Amavisd-new and will work out of the box. This does not mean that you cannot configure it via /etc/mail/spamassassin/local.cf, or your own cf-files in that directory.

3.1. ClamAV

ClamAV's configuration is stored in /etc/clamd.conf. We must edit /etc/clamd.conf to tell ClamAV that Amavisd-new will communicate using a local UNIX socket rather than a tcp socket, and where to find that socket. Edit the LocalSocket setting and comment out the TCPSocket like so:

### /etc/clamd.conf
#
# Set the LocalSocket for clam
# Note this *MUST* match that set in /etc/amavisd.conf
#
LocalSocket /var/run/clamav/clamd.sock
#
# Comment out the TCPSocket setting:
# TCPSocket 3310

3.2. Amavisd-new

Amavisd-new keeps it's configuration settings in /etc/amavisd.conf.

Due to the power and flexibility of Amavisd-new, there is actually quite a lot to look at, so we'll cover some of the more important settings a few at a time.

First up, we can disable either virus or spam checking by uncommenting the following lines (by default, both virus and spam checking is enabled as the lines are commented out):

### /etc/amavisd.conf:
#
# To disable virus or spam checks, uncomment the following:
#
# @bypass_virus_checks_maps = (1);  # controls running of anti-virus code
# @bypass_spam_checks_maps  = (1);  # controls running of anti-spam code
# $bypass_decode_parts = 1;         # controls running of decoders & dearchivers

Next, note to following lines although no change is required:

$max_servers = 2;                   # num of pre-forked children (2..30 is common), -m
$daemon_user  = "amavis";           # (no default;  customary: vscan or amavis), -u
$daemon_group = "amavis";           # (no default;  customary: vscan or amavis), -g
...
$inet_socket_port = 10024;          # listen on this local TCP port(s)
...
# $notify_method  = 'smtp:[127.0.0.1]:10025';
# $forward_method = 'smtp:[127.0.0.1]:10025';  # set to undef with milter!

$max_servers sets the number of concurrent Amavisd-new processes and must match the number set in /etc/postfix/master.cf "maxproc" column for the amavisfeed service (see configuration of Postfix below).

$daemon_user and $daemon_group should match the user and group, respectively, under which Amavisd-new will run.

$inet_socket_port defines the tcp port over which Amavisd-new will accept connections from Postfix.

$notify_method and $forward_method define the reinjection path of mail from Amavisd-new back into Postfix.

The following settings must be edited (in the case of $mydomain and $myhostname) and uncommented (remove the leading #):

$mydomain = 'example.com';                  # Edit: a convenient default for other settings
$MYHOME = '/var/amavis';                    # Uncomment: a convenient default for other settings, -H
$helpers_home = "$MYHOME/var";              # Uncomment: working directory for SpamAssassin, -S
$lock_file = "$MYHOME/var/amavisd.lock";    # Uncomment, -L
$pid_file  = "$MYHOME/var/amavisd.pid";     # Uncomment, -P
$myhostname = 'mail.example.com';           # Uncomment & Edit: must be a fully-qualified domain name!

Next up are some SpamAssassin settings which override the default SpamAssassin settings:

$sa_tag_level_deflt  = 2.0;                 # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 6.2;                 # add 'spam detected' headers at that level
$sa_kill_level_deflt = 6.9;                 # triggers spam evasive actions (e.g. blocks mail)
$sa_dsn_cutoff_level = 10;                  # spam level beyond which a DSN is not sent
# $sa_quarantine_cutoff_level = 25;         # spam level beyond which quarantine is off
$penpals_bonus_score = 8;                   # (no effect without a @storage_sql_dsn database)
$penpals_threshold_high = $sa_kill_level_deflt;         # don't waste time on hi spam
$sa_mail_body_size_limit = 400*1024;        # don't waste time on SA if mail is larger
$sa_local_tests_only = 0;                   # only tests which do not require internet access?

None of these need to be changed, but it's worthwhile being aware of them as this is the most convenient place to tweak spam thresholds.

$sa_tag_level_deflt is the level at which Amavisd-new will write spam info headers such as X-Spam-Flag, X-Spam-Score and X-Spam-Status. If you would always like header info to be written to all messages, set this value to -999.

$sa_tag2_level_deflt sets the level at which spam is tagged in the subject line of the message.

$sa_kill_level_deflt sets the level at which Amavisd-new will block the message and quarantine it. This is useful as SpamAssassin doesn't do this by default.

$sa_dsn_cutoff_level is the level at which delivery failure notices are no longer sent to the sender. As most spam sender addresses are forged anyway, it makes sense not to send failure notices in response to obvious spam as you're only contributing to the problem of backscatter.

$sa_quarantine_cutoff_level is the level at which spam isn't even quarantined. By default it is commented out meaning all spam will be quarantined.

Next up are some email addresses for notifications to be sent:

$virus_admin               = "virusalert\@$mydomain";   # notifications recip.
$mailfrom_notify_admin     = "virusalert\@$mydomain";   # notifications sender
$mailfrom_notify_recip     = "virusalert\@$mydomain";   # notifications sender
$mailfrom_notify_spamadmin = "spam.police\@$mydomain";  # notifications sender

You will probably want to set these to "postmaster\@$mydomain" or some other address you would rather receive spam notifications.

Finally, we need to uncomment the section for ClamAV like so:

### http://www.clamav.net/
['ClamAV-clamd',
  \&ask_daemon, ["CONTSCAN {}\n", "/var/run/clamav/clamd.sock"],
  qr/\bOK$/, qr/\bFOUND$/,
  qr/^.*?: (?!Infected Archive)(.*) FOUND$/ ],
# # NOTE: run clamd under the same user as amavisd, or run it under its own
# #   uid such as clamav, add user clamav to the amavis group, and then add
# #   AllowSupplementaryGroups to clamd.conf;
# # NOTE: match socket name (LocalSocket) in clamav.conf to the socket name in
# #   this entry; when running chrooted one may prefer socket "$MYHOME/clamd".

Note that the "/var/run/clamav/clamd.sock" setting must match the "LocalSocket /var/run/clamav/clamd.sock" we made earlier in /etc/clamd.conf.

3.3. Postfix

Next we need to configure the services in Postfix (/etc/postfix/master.cf) to allow mail to be passed to Amavisd-new for filtering and then reinjected back into Postfix.

First we will configure the Amavisd-new service to accept mail From Postfix. Amavisd-new supports both lmtp and smtp, and in this instance we have chosen to use the lmtp protocol. (FIXME: I'm not aware of any reasons for choosing one protocol over the other so selected to use lmtp on the basis that having local delivery to Amavisd-new show up in the logs as "lmtp" makes the log files somewhat easier to read).

Open /etc/postfix/master.cf and add the following service called "amavisfeed":

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
amavisfeed unix    -       -       n        -      2     lmtp
    -o lmtp_data_done_timeout=1200
    -o lmtp_send_xforward_command=yes
    -o disable_dns_lookups=yes
    -o max_use=20

Note that the number (2) in the "maxproc" column must match the $max_servers setting in /etc/amavisd.conf. For a detailed description of the options, see the Amavisd-new documentation (/usr/share/doc/amavisd-new-2.5.4/README.postfix.html).

Then we must define a dedicated service to reinject mail back into Postfix. For this we add an smtp service listening on localhost (127.0.0.1) tcp port 10025 (the default setting in /etc/amavisd.conf) to /etc/postfix/master.cf:

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
127.0.0.1:10025 inet n    -       n       -       -     smtpd
    -o content_filter=
    -o smtpd_delay_reject=no
    -o smtpd_client_restrictions=permit_mynetworks,reject
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o smtpd_data_restrictions=reject_unauth_pipelining
    -o smtpd_end_of_data_restrictions=
    -o smtpd_restriction_classes=
    -o mynetworks=127.0.0.0/8
    -o smtpd_error_sleep_time=0
    -o smtpd_soft_error_limit=1001
    -o smtpd_hard_error_limit=1000
    -o smtpd_client_connection_count_limit=0
    -o smtpd_client_connection_rate_limit=0
    -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters,no_address_mappings
    -o local_header_rewrite_clients=
    -o smtpd_milters=
    -o local_recipient_maps=
    -o relay_recipient_maps=

For a detailed description of the options, see the Amavisd-new documentation (/usr/share/doc/amavisd-new-2.5.4/README.postfix.html).

After making changes to /etc/postfix/master.cf, we must reload postfix for the changes to take effect:

postfix reload

At this point it might be wise to test the Amavisd-new and Postfix daemons are listening correctly (see the Testing section below).

Once everything is in place and working, the final step is to enable message filtering in Postfix by adding the following setting to /etc/postfix/main.cf:

content_filter=amavisfeed:[127.0.0.1]:10024

and reload postfix for the changes to take effect:

postfix reload

and watch your mail logs.

tail -f /var/log/maillog

3.4. Other MTA's

Amavisd-new can be configured with other MTA's besides Postfix. README docs for other MTA's can be found here:

http://www.ijs.si/software/amavisd/#doc

If anyone would like to contribute section's on configuring other MTA's they should see the How To Contribute page here:

http://wiki.centos.org/HowToContribute

4. Testing

Now would be a good time to test that the services we've defined are working as expected.

First, start the clamd and amavisd services:

# service clamd start
Starting Clam AntiVirus Daemon:                            [  OK  ]
# service amavisd start
Starting Mail Virus Scanner (amavisd):                     [  OK  ]

Now test that the amavisd service is listening on 127.0.0.1:10024 using telnet:

$ telnet localhost 10024
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
220 [127.0.0.1] ESMTP amavisd-new service ready
ehlo localhost
250-[127.0.0.1]
250-VRFY
250-PIPELINING
250-SIZE
250-ENHANCEDSTATUSCODES
250-8BITMIME
250-DSN
250 XFORWARD NAME ADDR PROTO HELO
quit
221 2.0.0 [127.0.0.1] amavisd-new closing transmission channel
Connection closed by foreign host.

If everything is working then you should see a successful connection similar to above.

Next to test the Postfix smtpd is listening on 127.0.0.1:10025:

$ telnet localhost 10025
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
220 mail.example.com ESMTP Postfix
ehlo localhost
250-mail.example.com
250-PIPELINING
250-SIZE 20480000
250-VRFY
250-ETRN
250-STARTTLS
250-ENHANCEDSTATUSCODES
250-8BITMIME
250 DSN
quit
221 2.0.0 Bye
Connection closed by foreign host.

Again we should see a successful connection as shown above. Now we can test if everything is working by sending special strings to test the scanning.

GTUBE (Generic Test for Unsolicited Bulk Email) string for testing SpamAssassin.

EICAR string for testing ClamAV.

Change directory to /usr/share/doc/amavisd-new-2.5.4/test-messages, and run:

perl -pe 's/./chr(ord($&)^255)/sge' <sample.tar.gz.compl | zcat | tar xvf -

to extract the sample test messages. Use these two commands to send a sample spam and virus message through:

$ sendmail -i your-address@example.com < sample-virus-simple.txt
$ sendmail -i your-address@example.com < sample-spam-GTUBE-junk.txt

Please replace "your-address@example.com" with your actual address. Check your mail log (i.e., /var/log/maillog) for the scan result from amavis and you should see something like "Passed SPAMMY" or "Blocked INFECTED (Eicar-Test-Signature)". You can now verify your settings match your delivery expectations.

5. SELinux

/!\ One workaround for SELinux issues is to temporarily use permissive rather than enforcing mode in  /etc/selinux/config . Such an approach has the added benefit of placing the needed information in the SELinux audit logs, which may be found in /var/log/audit/ . For more information, see this article about SELinux

When SELinux is enabled and in enforcing mode, some additional policies are required for amavisd and ClamAV. The following SELinux policy modules were determined by running the amavisd/ClamAV/SpamAssassin setup described herein on CentOS 5 (fully updated) with SELinux in permissive mode and running AVC error logs through audit2allow as described in the SELinux HowTo.

We are going to create two custom SELinux policy modules, amavisdlocal and clamlocal for amavisd and ClamAV, respectively (SpamAssassin does not require a custom SELinux policy). Cut and paste the following code and save to amavisdlocal.te and clamlocal.te, respectively:

 
module amavisdlocal 1.0;

require {
        type traceroute_port_t;
        type pgpkeyserver_port_t;
        type amavis_var_lib_t;
        type amavis_t;
        type clockspeed_port_t;
        class udp_socket name_bind;
        class lnk_file { read create unlink getattr };
}

#============= amavis_t ==============
allow amavis_t clockspeed_port_t:udp_socket name_bind;
allow amavis_t pgpkeyserver_port_t:udp_socket name_bind;
allow amavis_t traceroute_port_t:udp_socket name_bind;
allow amavis_t amavis_var_lib_t:lnk_file { read create unlink getattr };

 
module clamlocal 1.0;

require {
        type proc_t;
        type var_t;
        type sysctl_kernel_t;
        type clamd_t;
        class file { read getattr };
        class dir { read search };
}

#============= clamd_t ==============
allow clamd_t proc_t:file { read getattr };
allow clamd_t sysctl_kernel_t:dir search;
allow clamd_t sysctl_kernel_t:file read;
allow clamd_t var_t:dir read;
allow clamd_t var_t:file { read getattr };

Now build and load the amavisdlocal module:

# checkmodule -M -m -o amavisdlocal.mod amavisdlocal.te
checkmodule:  loading policy configuration from amavisdlocal.te
checkmodule:  policy configuration loaded
checkmodule:  writing binary representation (version 6) to amavisdlocal.mod
# semodule_package -o amavisdlocal.pp -m amavisdlocal.mod
# semodule -i amavisdlocal.pp

and repeat for clamlocal:

# checkmodule -M -m -o clamlocal.mod clamlocal.te
checkmodule:  loading policy configuration from clamlocal.te
checkmodule:  policy configuration loaded
checkmodule:  writing binary representation (version 6) to clamlocal.mod
# semodule_package -o clamlocal.pp -m clamlocal.mod
# semodule -i clamlocal.pp

Finally, check that our custom local SELinux policy modules are loaded:

# semodule -l
amavis  1.1.0
amavisdlocal    1.0
ccs     1.0.0
clamav  1.1.0
clamlocal       1.0
dcc     1.1.0
evolution       1.1.0
iscsid  1.0.0
mozilla 1.1.0
mplayer 1.1.0
nagios  1.1.0
oddjob  1.0.1
pcscd   1.0.0
postgrey        1.0
pyzor   1.1.0
razor   1.1.0
ricci   1.0.0
smartmon        1.1.0

5.1. CentOS 6

For CentOS 6, additional steps are required. Thanks to Harald Oehlmann in http://lists.centos.org/pipermail/centos-docs/2012-October/004994.html

Amavis is storing the message body and all attachements (subfolder "parts") in a subfolder of "/var/amavis/tmp". The virus scanner is scanning those files and writes its result in files in this folder.

Virus Scanner action on this folder is stopped by SELinux, resulting in errors like "(!)run_av (ClamAV-clamscan) FAILED" in "/var/log/mail".

Do the following to allow this interface with clam-av:

--se_clamav_amavis.te--
# ***HaO 2012-09-30: add rule to allow clamav to access amavis files
# and writes back ok file and may create temp folder
module clamscanamavis 1.0;
require {
        type clamscan_t;
        type amavis_var_lib_t;
        class file {getattr read open write create unlink};
        class dir {search read getattr open write add_name create
setattr remove_name rmdir};
}
allow clamscan_t amavis_var_lib_t:file {getattr read open write create
unlink};
allow clamscan_t amavis_var_lib_t:dir {search read getattr open write
add_name create setattr remove_name rmdir};

And then

checkmodule -M -m -o se_clamav_amavis.mod se_clamav_amavis.te
semodule_package -o se_clamav_amavis.pp -m se_clamav_amavis.mod
semodule -i se_clamav_amavis.pp

6. Updating

6.1. SpamAssassin

Spam is rapidly changing, and new rules are often written in response. With sa-update, those rules can quickly (potentially within minutes) be distributed and the new spam caught. Please read about sa-update before continuing. To enable automatic updates, open up /etc/cron.d/sa-update in your favorite editor and uncomment the cron line so it looks like this:

10 4 * * * root /usr/share/spamassassin/sa-update.cron 2>&1 | tee -a /var/log/sa-update.log

Save and exit. This cron job will run at 4:10AM everyday.

6.2. ClamAV

ClamAV uses freshclam to update the virus definitions. They are automatically updated with the /etc/cron.daily/freshclam cron script. No actions need to be taken. You can verify that your updates are being completed by looking at your /var/log/clamav/freshclam.log log file.

7. Links

Amavisd-new is supplied with extensive documentation installed to /usr/share/doc/amavisd-new-2.5.4/ and the reader is referred to /usr/share/doc/amavisd-new-2.5.4/README.postfix.html in particular. An online version is also available here: http://www.ijs.si/software/amavisd/README.postfix.html

http://www.linuxjournal.com/article/7778

http://www200.pair.com/mecham/spam/clamav-redhat-amavis.html

http://www200.pair.com/mecham/spam/clamav-amavisd-new.html

...

HowTos/Amavisd (last edited 2013-01-04 17:05:38 by NedSlider)