1.0 Introduction

This is the fifth in a series of six posts explaining how I set up an email server on CentOS 7. It will assume you’ve read the first, second, third and fourth in the series before now and taken the steps described there.

In this installment, we’ll be adding anti-spam and anti-virus capabilities to our mail server, so that it can stop you being overwhelmed by time-wasting or potentially dangerous emails. At this stage in the process of mail server configuration, we won’t stop spam mail being delivered to your inbox, but we will at least mark it clearly. In a subsequent installment, we’ll use that marking as a way of hiding spam from you altogether.

2.0 Installation of Anti-Spam measures

There is pretty much only one viable tool in Linux for killing spam -and that’s “spam assassin”. It’s very easy to install, rather trickier to integrate with the other components on your mail server!

As root, then, install it like so:

yum -y install spamassassin

Basic spamassassin configuration should then be performed like so:

nano /etc/mail/spamassassin/local.cf

Delete anything that’s already there and paste this lot in its place:

required_hits 3
report_safe 0
rewrite_header Subject ***SPAM***
required_score 3

These are the settings I use; you can vary them as you prefer. The critical one for me is “required_score”: this defaults to 5 and is the number of ‘spam points’ a received email needs to be given before it is considered -and marked- as spam. I found the default value a bit high, with quite a lot of advertisements for wristwatches making it through unmarked. So I’ve settled on a score of 3, which seems to trap most things I need it to, without any very obvious false positives so far. As I say, you’ll have to experiment with it to get it to a value that makes it do what you need it to do. (Incidentally, required_hits was the old name for the new required_score parameter, so they should be set identically).

Should an email accumulate the necessary score, its subject will be prefixed with whatever you set the ‘rewrite_header’ parameter to. In my case, it will prefix with a three-asterisk ‘SPAM’ marker. I’ll use that later on to filter my email so that I don’t even get to see spam email -but to begin with, spamassassin will simply be re-labelling my email in this way so that I can see it’s working.

Once you’ve saved this configuration file, finish things off by issuing these commands:

groupadd spamd
useradd -g spamd -s /bin/false -d /var/log/spamassassin spamd
chown spamd:spamd /var/log/spamassassin

That just creates a new ‘spamd’ user who is then given permission to write spam detection logs into the /var/log/spamassassin directory.

3.0 Integration with Postfix

Spamassassin has been told how to score and re-label email: fine. But we also need to tell our mail transport agent (i.e., Postfix) to pass all email through spamassassin’s hands before attempting to deliver it.

To do that, we need to edit Postfix’s configuration as follows:

nano /etc/postfix/master.cf

Change line 11 (or so -it may vary!) from this:

smtp      inet  n       -       n       -       -       smtpd

…to this:

smtp      inet  n       -       n       -       -       smtpd -o content_filter=spamassassin

That is, you’re adding an “option” (hence the use of the ‘o’ switch) to pass email received via smtp to the spamassassin filter.

Now you add this to the very end of the same file to specify where the spamassassin filter works and how to interact with it:

spamassassin unix - n n - - pipe
 flags=R user=spamd argv=/usr/bin/spamc -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}

4.0 Finishing things up

You now just need to make sure that spamassassin is up-to-date: updates to its how-to-score rules are published quite often by the tool’s developers, so it’s useful to make sure you’ve got the latest ruleset. As root, just type:


…and follow the prompts, if any. Once it’s been successfully updated, you should schedule the same thing to happen regularly. I do that as follows:

mkdir -p /root/logs
mkdir -p /root/scripts

export EDITOR=nano

crontab -e

So I create a couple of directories for things to be recorded in (I always create these two directories on any Linux server I build, actually!) Then I edit the crontab for the root user. Onto the end of whatever already exists in the crontab file now displayed, paste this:

# Update Spamassassin on the 1st day of each month at 1AM
0 1 1 * * /bin/sa-update --nogpg >> /root/logs/saupdate.log

That ensures that the spamassassin update takes place on the 1st day of every month at 1AM… you might want to fine-tune the precise timing to better suit your needs, but once a month or so is about the right frequency for this to be happening.

Finish things off by restarting everything and making sure that Spamassassin is configured to restart every time your server bounces:

systemctl restart postfix
systemctl restart dovecot
systemctl enable spamassassin
systemctl start spamassassin

And now you should have a working spam trap on your mail server.

5.0 Testing it

To test if your new spam filter is working, you can use a third-party emailing capability (such as your gmail or outlook.com account) to send an email to your new self-managed email address with a subject line that contains the string:


An alternative is to visit a site that deliberately sends you a small number of spam emails, such as this one.

Otherwise, just sit back and wait a bit: the state of email these days is such that you really don’t have to test anti-spam measures. Given enough time, they’ll be tested (for real!) whether you like it or not!

However you do it, you should see this sort of thing coming through:


Notice how the email has had its subject re-written to include, in big, bold letters, the ‘***SPAM***’ designation I set earlier? So now you can click on that and then click the Junk button as before and have it moved to the Junk folder. We haven’t yet got to the point where such messages are automatically moved to the Junk folder, though: all we’ve done is enable potential spam to be highlighted so that it’s easier for you to sort and filter your way through them. We’ll get automatic filtering sorted soon.

6.0 Add Anti-virus

Now we come to the business of adding anti-virus to your mail server. It’s important to do because although your mail server isn’t a Windows machine and is therefore immune to 99% of all the viruses being mailed about the place, you don’t want your mail server to get a reputation for passing along infected mails.

So that’s what we’ll add next, as follows:

yum -y install amavisd-new clamav clamav-update clamav-server clamav-data clamav-update clamav-filesystem clamav clamav-scanner-systemd clamav-devel clamav-lib clamav-server-systemd

As you can see, ‘clamav’ is our antivirus of choice. It’s linked into our Postfix/Dovecot infrastructure by the ‘amavis’ tool (which is a cutesy name meaning ‘a mail virus scanner’). Both those tools will need a bit of configuring, as follows…

First, we have to tell clamav that it is running on a ‘proper’ server, not just a test one:

nano /etc/freshclam.conf

Find the line which reads Example and stick a hash character in front of it, to comment it out.

You also need to edit a second configuration file as follows:

nano /etc/sysconfig/freshclam

You will find four lines down at the very bottom of this new file:

### !!!!! REMOVE ME !!!!!!
### REMOVE ME: By default, the freshclam update is disabled to avoid
### REMOVE ME: network access without prior activation

Your job is to delete all of them (Ctrl+K deletes whole lines at a time in nano). Once you’ve saved the edited file, you can issue the command to update the clamav install:


It will take quite a few minutes to complete, and there’ll be scary-looking warnings about installations being out of date, files being corrupted and so on. Let it alone and it will sort itself out!

Next, edit another configuration file:

nano /etc/clamd.d/scan.conf

There are two edits to be made here. First, at the top of the file, as before, comment out the line which reads Example. Secondly, find the line LocalSocket /var/run/clamd.scan/clamd.sock and un-comment it. Nothing else needs editing in this particular file. Now enable the clamav daemon:

systemctl enable clamd@scan
systemctl start clamd@scan
systemctl status clamd@scan

The last line should return you quite a lot of output, similar to this:

[root@mail ~]# systemctl status clamd@scan
  clamd@scan.service - Generic clamav scanner daemon
   Loaded: loaded (/usr/lib/systemd/system/clamd@scan.service; enabled; vendor preset: disabled)
   Active: active (running) since Fri 2016-06-24 13:49:07 AEST; 6s ago
 Main PID: 7289 (clamd)
   CGroup: /system.slice/system-clamd.slice/clamd@scan.service
           └─7289 /usr/sbin/clamd -c /etc/clamd.d/scan.conf --nofork=yes

Jun 24 13:49:07 mail.dizwell.com systemd[1]: Started Generic clamav scanner daemon.
Jun 24 13:49:07 mail.dizwell.com systemd[1]: Starting Generic clamav scanner daemon...
Jun 24 13:49:07 mail.dizwell.com clamd[7289]: clamd daemon 0.99.1 (OS: linux-gnu, ARCH: x86_64, CPU: x86_64)
Jun 24 13:49:07 mail.dizwell.com clamd[7289]: Running as user clamscan (UID 992, GID 990)
Jun 24 13:49:07 mail.dizwell.com clamd[7289]: Log file size limited to 1048576 bytes.
Jun 24 13:49:07 mail.dizwell.com clamd[7289]: Reading databases from /var/lib/clamav
Jun 24 13:49:07 mail.dizwell.com clamd[7289]: Not loading PUA signatures.
Jun 24 13:49:07 mail.dizwell.com clamd[7289]: Bytecode: Security mode set to "TrustSigned".

All that basically means you now have an antivirus scanner running in the background on your mail server.

To finish things off on the clamav side of things, let’s arrange for it to be regularly updated with fresh anti-virus signatures. First, we make sure that the relevant configuration file allows automatic updates:

nano /etc/cron.d/clamav-update

What you need to see is something like this:

## Adjust this line...

## It is ok to execute it as root; freshclam drops privileges and becomes
## user 'clamupdate' as soon as possible
0  */3 * * * root /usr/share/clamav/freshclam-sleep

Specifically, you want to make sure that the last line of that lot is NOT commented out. It is, as you probably recognise, using crontab syntax to ensure that clamav updates itself every three hours.

7.0 Integrating antivirus with Postfix

So what now remains is to make sure that Postfix and Dovecot know how to work with Clamav so that the virus scanner gets to check on mail passing through the system… and this is where we need to configure amavis.

To begin:

nano -c /etc/amavisd/amavisd.conf

Around line 20 of this interminable file (and the need to know line numbers is why I’ve just invoked nano with the ‘-c’ switch) is this:

$mydomain = 'example.com';   # a convenient default for other settings

Change that to read:

$mydomain = 'dizwell.com';   # a convenient default for other settings

Similarly, at around line 152 is an entry for $myhostname. Uncomment that line and set it to the fully-qualified host name for your server (i.e., in my case, mail.dizwell.com).

Finally, at line 159, change the final destination for spam delivery to ‘D_PASS’ (because you’ll recall from Episode 1 that the requirement is to have a spam filter that identifies spam but doesn’t actually block it. I don’t trust spam blockers to be perfect, and I’d rather not have important emails failing to be delivered at all). So change this:

$final_virus_destiny      = D_DISCARD;
$final_banned_destiny     = D_BOUNCE;
$final_spam_destiny       = D_DISCARD;  #!!!  D_DISCARD / D_REJECT
$final_bad_header_destiny = D_BOUNCE;


$final_virus_destiny      = D_DISCARD;
$final_banned_destiny     = D_BOUNCE;
$final_spam_destiny       = D_PASS;  #!!!  D_DISCARD / D_REJECT
$final_bad_header_destiny = D_BOUNCE;

This means that if spam is detected, it will still be passed on to you. I

Finally, scroll to around line 94 and change the value for detecting spam to a value of ‘3’, as we did with plain Spamassassin before:

$sa_tag_level_deflt  = 3.0;  # add spam info headers if at, or above that level

Next, a little bit of Postfix re-configuration is required:

nano /etc/postfix/master.cf

We left it before with an “smtp” line that read:

smtp      inet  n    -    n    -    -    smtpd -o content_filter=spamassassin

Your job is now to alter that line so that we use amavis as our content filter, not spamassassin. So alter the line so that it reads:

smtp      inet  n    -    n    -    -    smtpd -o content_filter=smtp-amavis:

(That will probably wrap here for formatting reasons, but it’s actually all just one long line, and the bit you’re adding to what’s already there I’ve marked in bold).

Finish off by pasting this lot right at the very end of the same file:

smtp-amavis  unix  -    -       y       -       2       smtp
 -o smtp_data_done_timeout=1200
 -o disable_dns_lookups=yes
 -o smtp_send_xforward_command=yes inet n  -       y       -       -       smtpd
 -o content_filter=
 -o smtpd_helo_restrictions=
 -o smtpd_sender_restrictions=
 -o smtpd_recipient_restrictions=permit_mynetworks,reject
 -o mynetworks=
 -o smtpd_error_sleep_time=0
 -o smtpd_soft_error_limit=1001
 -o smtpd_hard_error_limit=1000
 -o receive_override_options=no_header_body_checks
 -o smtpd_helo_required=no
 -o smtpd_client_restrictions=
 -o smtpd_restriction_classes=
 -o disable_vrfy_command=no
 -o strict_rfc821_envelopes=yes

Now all you have to do is stop and restart all the relevant services:

systemctl stop postfix
systemctl start amavisd 
systemctl enable spamassassin
systemctl enable amavisd
systemctl restart clamd@scan.service
systemctl start postfix

To test it works, use a third-party email service to send yourself an email with this string in its body (not its subject!):


Once you’ve sent it, take a look at your /var/log/maillog and you should see something like this appear in it:

[root@mail ~]# tail /var/log/maillog 
Jun 24 07:07:10 mail postfix/smtpd[7938]: disconnect from mail-io0-f176.google.com[]
Jun 24 07:07:10 mail clamd[7627]: SelfCheck: Database status OK.
Jun 24 07:07:10 mail clamd[7627]: /var/spool/amavisd/tmp/amavis-20160624T150710-07772-RMId9mdH/parts/p004: Eicar-Test-Signature FOUND
Jun 24 07:07:10 mail clamd[7627]: /var/spool/amavisd/tmp/amavis-20160624T150710-07772-RMId9mdH/parts/p001: Eicar-Test-Signature FOUND
Jun 24 07:07:10 mail clamd[7627]: /var/spool/amavisd/tmp/amavis-20160624T150710-07772-RMId9mdH/parts/p002: Eicar-Test-Signature FOUND
Jun 24 07:07:10 mail amavis[7772]: (07772-01) Blocked INFECTED (Eicar-Test-Signature) {DiscardedInbound,Quarantined}, []:3489

If you see the ‘blocked infected’ message, the anti-virus content filter is working.

Try another spam test as well (i.e., send an email with “XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X” in its subject) and you should see that does get delivered, but it also has its subject line altered to clearly mark it as spam.

8.0 Conclusion

So this is a good place to stop this installment. We now have virus-laden emails simply being not delivered at all and spam being marked as such, but delivered for the client to process.

At this point, you’ve got a working webmail server that is capable, robust and doing the job it was designed for!

There are just a few problems with it. Firstly, you can only get to the roundcube web page by explicitly asking for it (that is, your browser has to be told to visit ‘dizwell.com/roundcube’, whereas it would be quite nice just to be able to visit ‘mail.dizwell.com’).

Secondly, it’s quite bad news to be visiting a mail URL using “http” at all. It means that as you log on, your credentials are being passed in plain text. Much better would be to make your webmail server’s Apache setup use https (i.e., secure http) by default.

And finally: my inbox is now being flooded by spam messages all neatly marked as ‘***SPAM***’! It would be much better if we could set up client-side filters to send all such mail to the Junk folder -searchable and readable if you have to, but not clogging up the inbox itself.

So in the final part of this series, I’ll re-configure Apache to run securely, have a convenient way of not having to specify a long URL to reach it, and add filtering to the Roundcube client.