(Note: This guide was originally published 2009-11-04 and has not been updated)

Here’s a basic guide to installing an inbound mail filter using Postfix, Amavisd-new, and ClamAV on Ubuntu 8.04 or 8.10, that forwards to an internal mail system (Exchange for example).

Install Ubuntu on your machine (I highly recommend doing this on a VM or spare box). You can get Ubuntu here. Note that 9.10 was just released, so you’ll want to get 8.04LTS.

Follow the install defaults, and when you get to package installation, just install OpenSSH for now; we’ll deal with the other packages we want later.

Once you’ve got Ubuntu installed, log in at the console again, not via SSH yet. First off, update the package list: sudo apt-get update

Now install the build-essential package: sudo apt-get install build-essential

Once that’s finished let’s apply all the system security updates: sudo apt-get upgrade. You should notice two packages held back with names like this: linux-kernel-2.x.x.x. This is normal; kernel upgrades have to be explicitly specified.

Okay, now you’ve got an up to date system. Lets install the programs we are going to use.

Install Postfix: sudo apt-get install postfix

Postfix will ask you a couple questions; install as an Internet Site, and use the local system name.

Install SpamAssassin: sudo apt-get install spamassassin

Install Pyzor and Razor: sudo apt-get install pyzor razor

Install Amavisd-new: sudo apt-get install amavisd-new

Now, we need to install ClamAV, but the default repository for Ubuntu 8 doesn’t have the latest release; fortunately some generous souls have backported it from Ubuntu 9. So lets enable the backports repository.

Run sudo vim /etc/apt/sources.list and find the two lines that contain intrepid-backports. There’s usually a block of text above each set of sources that explains what it is. Save and quit the vim editor.

Now, you’ll need to refresh apt’s cache again, so run sudo apt-get update to update the package lists. This will add in the locators for the backported packages.

Install ClamAV: sudo apt-get install clamav clamav-daemon

We also need some decompression libraries so ClamAV can scan inside compressed files.

Install libraries: sudo apt-get install arj bzip2 cabextract cpio file gzip lha nomarch pax rar unrar unzip zip zoo

(By now you’ve probably figured out that you could install all this stuff with one command if you listed all the package names…)

Now, the default configuration of ClamAV and Amavisd from the Ubuntu repositories are pre-configured to work with each other (thanks to whoever put those packages together!). We do need to make a few changes…

ClamAV and Amavisd need to be able to access each other’s files:

Run: sudo adduser clamav amavis

Run: sudo adduser amavis clamav

(Under Ubuntu, each user also has a group with the same name.)

Okay, now we have our programs installed, lets get them doing something!

First off, lets configure Spamassassin:

Run: sudo vim /etc/default/spamassassin and change the ENABLED=0 to ENABLED=1. This enables daemon mode, which is much more memory efficient.

Run: sudo vim /etc/spamassassin/local.cf and uncomment lock_method flock. We are going to keep a local database. NOTE: If you are using an NFS mount to store the data do NOT uncomment this line. You’ll fry your database. If you’re using NFS and reading this guide though, you probably should already know this.

Since we are using Amavis to do the program linking, the majority of the configuration of Spamassassin is there, rather than in Spamassassin’s configuration files. So, lets dig into Amavisd.

Now, the set up of Amavisd under Ubuntu uses split configuration files, as opposed to previous versions and other distributions which use a single monolithic file. Either way works, but the split file method does allow for easier overridding of defaults. If you look in /etc/amavis/conf.d/ you’ll find the configuration files. The higher numbered files override the lower number files, so generally your customizations should be placed in 50-user.

That said, I find it easier to do some customizations directly in the default files. Just remember to document them so you can upgrade to newer versions easily without trying to diff everything!

First off, sudo vim 05-domain_id. Change the local_domains_acl line to contain yourdomain.ext, where that is the domain you are going to receive mail for. If you have multiple domains to receive mail for, separate each domain with a comma, and surround each domain name with single quotes. Save and quit the file once this is done.

Now lets edit the 20-debian_defaults file: sudo vim 20-debian_defaults. Most of the items in here we are going to override later, but there is one section I generally edit here: banned files. Scroll down through the file until you see a section with a banned files comment, and look for a line similar to this: qr’.\.(exe|vbs|pif|scr|bat|cmd|com….)$’i, #banned extension – basic. Add any additional file types you want to ban from being received via e-mail. Separate each extension with a pipe. Save and quit the file once you are happy with it.

Now for the meaty bit, 50-user. Open it for editing: sudo vim 50-user. First thing to do is set our spam kill levels:

$sa_spam_subject_tag = ‘***SPAM*** ‘;
$sa_tag_level_deflt  = undef;  # always add spam headers
$sa_tag2_level_deflt = 3.5; # add spam to subject tag and forward message
$sa_kill_level_deflt = 5; # quarantine message and do not forward
$sa_dsn_cutoff_level = 10;   # spam level beyond which a DSN is not sent

This always adds the spam headers to each message so you can see the analysis done by Spamassassin, even if the message is not flagged as spam. This is useful for tracking down false positives and negatives.

Now, we don’t want to bounce e-mails with banned extensions, sometimes we may actually want them. Add this line:

$final_banned_destiny  = D_DISCARD;

This ‘discards’ the message without sending an NDR to the sender. D_DISCARD also means the message is quarantined on the server, not actually deleted.

Depending on how busy the mail server will be, you may need more than the default number of amavisd-new processes. I usually configure 4 processes and find this is enough for most servers.

$max_servers = 4;

There are also notification options for when an email containing a banned file or virus is blocked. You can set these or not as you wish:

    $virus_admin = “administratorname\@example.com”; #administrator address for virus block notifications
$warnvirusrecip = 1;  # notify recipient of virus
$warnbannedrecip = 1; # notify recipient of banned file type

$mailfrom_notify_admin = “filter\@mailserver.example.com”; #identifying email address for warning messages to administrator
$hdrfrom_notify_sender = “filter\@mailserver.example.com“; #if you send sender notifications, we usually don’t
$hdrfrom_notify_admin = “filter\@
mailserver.example.com“;

Now, Amavisd-new is configured, lets get Postfix configured. Postfix configuration is done in two key files: main.cf and master.cf.

First off, lets work with main.cf: sudo /etc/postfix/main.cf

I like to change the email banner announcing the server to identify less information: smtpd_banner = $myhostname ESMTP

I also disable TLS, unless you are going to actually set up certificates: smtpd_use_tls = no

Set the hostname: myhostname = MAIL1.EXAMPLE.COM

Set the origin domain for mail from this server: myorigin = EXAMPLE.COM

Since we are going to forward to an internal mail system set: mydestination =

Yes, it should be blank.

Set the domains you are going to accept mail for and send to the internal system: relay_domains = example.com example.org

You need to tell Postfix how to get to your internal mail server. You can either do this with MX records, if it can access your internal DNS system, or you can hard-code the relay address. Hard-coding works for most setups with a single server: transport_maps = hash:/etc/postfix/transport. We’ll create the transport file later.

If you are putting this server behind a NAT device, Postfix will need to know what external IP address it is sending as: proxy_interfaces = EXTERNAL.IP.ADDRESS.HERE

Now, Postfix needs to know how to send messages to Amavisd for filtering: content_filter = smtp-amavis:[127.0.0.1]:10024

Lets not tell people how we are selecting valid users: show_user_unkown_table_name = no

Now, you need a way to reject email addresses that aren’t valid. I use a script that generates a list of valid addresses from my Exchange server. You’ll need something similar to this: relay_recipient_maps = hash:/etc/postfix/valid_recipients. The valid_recipients file should contain e-mail addresses with the format: user@example.com OK to accept mail destined for that address.

Now, lets set up some filtering to block spam before it even gets to SpamAssassin:

   smtpd_client_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_rbl_client zen.spamhaus.org=127.0.0.2,
reject_rbl_client zen.spamhaus.org=127.0.0.4,
reject_rbl_client zen.spamhaus.org=127.0.0.5,
reject_rbl_client zen.spamhaus.org=127.0.0.6,
reject_rbl_client zen.spamhaus.org=127.0.0.7,
reject_rbl_client zen.spamhaus.org=127.0.0.8,
reject_rbl_client zen.spamhaus.org=127.0.0.10,
reject_rbl_client zen.spamhaus.org=127.0.0.11,
permit

This uses the SpamHaus ZEN RBL to block systems that shouldn’t be sending mail, or are known spam/malware/viral senders. MAKE SURE YOU READ THE SPAMHAUS TERMS OF USE. Larger mail servers will need to pay for this service.

And a couple more filters to weed out bad servers:

smtpd_recipient_restrictions =
permit_mynetworks
reject_unauth_destination
reject_unauth_pipelining

smtpd_sender_restrictions =
permit_mynetworks
reject_non_fqdn_sender

Now, I translate mail for local UNIX services to send to my administrator account:

   local_transport = virtual
local_recipient_maps = $virtual_mailbox_maps

   virtual_alias_maps = hash:/etc/postfix/virtual

Now, save the file and quit the editor.

Okay, almost done! Lets configure the Postfix services. Remeber that content_filter line we added? We need to define that now.

Open up master.cf: sudo vim /etc/postfix/master.cf

Look for the line that contains pickup. Add these lines right under it:

-o content_filter=
-o receive_override_options=no_header_body_checks

Now scroll down to the end of the file and add:

smtp-amavis     unix    –       –       –       –       4       smtp
-o smtp_data_done_timeout=1200
-o smtp_send_xforward_command=yes
-o disable_dns_lookups=yes
-o max_use=20

127.0.0.1:10025 inet    n       –       –       –       –       smtpd
-o content_filter=
-o local_recipient_maps=
-o relay_recipient_maps=
-o smtpd_restriction_classes=
-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 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

Remember my comment about the number of amavis-d processes? The max_servers number from Amavisd’s configuration files needs to match the maxproc column in Postfix’s master.cf. This is the second from last column in this file (there’s a 4 above already, if you’ve copied and pasted my settings from this article, you don’t need to change anything.)

Save the file and quit. Postfix configuration is done, we just need to create the data files we specified!

Lets set up the transport table first, so Postfix knows where to send stuff:

sudo vim /etc/postfix/transport

Create lines with the following format, one per domain: example.com   smtp:[IP.ADD.GOES.HERE]

In case you are wondering, placing an IP address in square brackets means to bypass the MX record lookup and just connect to that address.

Save the file and quit the editor once you have your entries.

Now, anything you specify a hash table in Postfix, you need to create the actual database file Postfix reads. Run sudo postmap /etc/postfix/transport. This will create the transport.db file, which Postfix will use for it’s lookups.

Do the same for the virtual file: sudo vim /etc/postfix/virtual. Add local users and the email address to redirect to:

root    administrator@example.com
amavis    administrator@example.com

Save and quit, then run the postmap command against the virtual file.

We’re pretty much done. Restart the server for expediency; you could restart each service, but since this is a new build, lets be lazy. sudo shutdown -r now.

Your server should be set up and ready to filter mail! Too bad Spamassassin doesn’t know anything about the kind of spam you receive. What can you do about this? Well, if you have a collection of spam email, you can train SpamAssassin with them. You do this with the sa-learn command. For example, if you have a number of spam messages in mbox format, you can train them with the following command:

sa-learn –spam –mbox –progress /path/to/spam.box

IMPORTANT NOTE: With this server, you MUST do the sa-learn as the amavis user. Otherwise it will not get imported correctly. Also, the messages must not be forwarded or have their headers otherwise mangled; Spamassassin relies on the original headers.