Gentoo Wiki


Complete Virtual Mail Server

Getting Started

Basic Mail Setup

Enhanced Mail Services

Anti-Spam Configuration

Anti-Virus Configuration

Log Analyzer

Wrapping it Up


Anti-Spam Configuration

We are going to use a number of elements to secure this system from SPAM. Most people slap in a single filter and are done with it, however as we are building a secure and rather robust system, we are going to go a little further than your average bear on this one.

Before we dig in on this, it is really worth taking some time to figure out how all of this hangs together. The configuration I am using here will give the ability to offer per-recipient options (max flex on this installation) which is more complicated then your standard install. If you do not understand how it works, you will get it wrong and fixing it will be a long slog.

The best way to start is with a picture that shows where all the different pieces snap together. Notice that mail will loop through the system a couple of times. The first time as it comes in, it will be routed off to Amavisd-new for Spam and Anti-virus filtering (path denoted by red arrows). It is then passed back into Postfix where it is routed as local mail and into the mailbox.

The one missing component is a UI that will allow us to manage the database. I have looked at many, but have not found anything yet that will meet the needs. Further on in this section, I discuss some of the options and will leave it to you to figure out what will work. Based on the diagram, you can see that there is a fair bit of work required here, so lets tuck in and get to it.


Installing Amavisd-new and SpamAssassin

We are going to need a mechanism to glue together all the different mail processing programs we are going to run. Amavisd-new will serve just fine in this capacity, giving us the ability to hook in. To facilitate the configuration, I will emerge both amavisd and SpamAssassin and set them up together.

Code: Installing amavisd-new
# emerge amavisd-new spamassassin

Don’t be surprised if there are a number of packages being installed here. A mitt-full of Perl modules are required to make this all work so the install will take a bit of time.

All of amavisd’s configuration parameters are contained the file /etc/amavisd.conf. As we step through and identify the changes we will be making, be sure to take the time to read the comments in the config file as time has been spent really documenting this one. The config file is really well laid out, broken into 9 sections. We will go through each of them as required, outlining the specific things you will need to pay attention to.

Section I – Essential Daemon and MTA Settings

Many of the key parameters in this section are automatically set by the ebuild, but we will go check them anyway.

Code: Installing /etc/amavisd.conf
# nano –w /etc/amavisd.conf

$MYHOME = '/var/amavis';   # (default is '/var/amavis')

$mydomain = ''   # (or whatever domain you are using)

# @bypass_spam_checks_maps = (1);  # uncomment to DISABLE anti-spam code

$mydomain does not need to include all of the domains you will be scanning for, just the one you use to refer to your mail server.

Amavisd assumes that a linux user and group (‘amavis’) have been setup so be sure and check that it was successfully completed by portage. I quickly popped over to webmin and used the “User and Group” view under “System” to verify they had been added.

Leave the @bypass_spam_checks_maps line commented to keep the spam filtering turned on.

Now lets setup for our MTA as appropriate. We need to tell Amavis to send mail back to Postfix on port 10025 as we have specified in the diagram above. Since Amavis and Postfix run on the same server, we are going to route it back to itself.

Note: THe $forward_method and $notify_method are default settings in amavisd-new if you are running a recent version. Just check to make sure.
Code: Installing /etc/amavisd.conf
# POSTFIX, or SENDMAIL in dual-MTA setup, or EXIM V4
# (set host and port number as required; host can be specified
# as an IP address or a DNS name (A or CNAME, but MX is ignored)

$forward_method = 'smtp:[]:10025'; # where to forward checked mail

$notify_method = $forward_method;       # where to submit notifications

The rest of this section will be left at their default levels (most things are commented out so don’t worry about them).

Section II – MTA Specific

Here we will adjust the parameters as required to support Postfix. Start by commenting out the $unix_socketname as this is not required by Postfix.

Code: Installing /etc/amavisd.conf
# AMAVIS-CLIENT PROTOCOL INPUT SETTINGS (e.g. with sendmail milter)
# (used with amavis helper clients like amavis-milter.c and amavis.c,
# NOT needed for Postfix or Exim or dual-sendmail - keep it undefined.
# $unix_socketname = "$MYHOME/amavisd.sock";
# $unix_socketname = undef;   # disable listening on a unix socket
                              # (default is undef, i.e. disabled)
                              # (usual setting is $MYHOME/amavisd.sock)

Next, be sure that local port is setup for amavis to receive messages on (port 10024). It should already be set to this, but you will want to double check. We will also bind amavisd to the local server so that we do not allow open access to amavis’ SMTP port.

Code: Installing /etc/amavisd.conf
# SMTP SERVER (INPUT) PROTOCOL SETTINGS (e.g. Postfix, Exim v4, ...)
#(used when MTA is configured to pass mail to amavisd via SMTP or LMTP)
$inet_socket_port = 10024;        # accept SMTP on this local TCP port
                                  # (default is undef, i.e. disabled)

# SMTP SERVER (INPUT) access control
# - do not allow free access to the amavisd SMTP port !!!
# when MTA is at the same host, use the following (one or the other or both):
#$inet_socket_bind = ''; # limit socket bind to loopback interface
                                  # (default is '')
@inet_acl = qw( [::1]);  # allow SMTP access only from localhost IP
                                  # (default is qw( [::1]) )

The rest of this section should be commented out for our setup.

Section III – Logging

We are only going to make a few changes here to assist us when testing and debugging, unless of course you feel that this HOWTO is so well written that everything will work the first time. Hmmm .. didn’t think so.

We will set up to dump into a separate log file and add boost levels to include some debug information. Have a read through the comments as there is a lot of really good information here to help guide you.

Code: Installing /etc/amavisd.conf
# true (e.g. 1) => syslog;  false (e.g. 0) => logging to file
$DO_SYSLOG = 0;                   # (defaults to 0)
$SYSLOG_LEVEL = 'mail.debug';     # (facility.priority, default '')

# Log file (if not using syslog)
$LOGFILE = "$MYHOME/amavis.log";  # (defaults to empty, no log)

#NOTE: levels are not strictly observed and are somewhat arbitrary
# 0: startup/exit/failure messages, viruses detected
# 1: args passed from client, some more interesting messages
# 2: virus scanner output, timing
# 3: server, client
# 4: decompose parts
# 5: more debug details
$log_level = 5;           # (defaults to 0)

Section IV – Notifications/DNS, destiny, quarantine

This part of the setup covers a few areas so follow closely as you will be jumping over large parts of the config file. Feel free to have a read along the way and use google to lookup anything you do not understand.

In our setup we will be using the quarantine capabilities, so we want to tell Amavis how to handle spam, virus or banned messages. As we are using Postfix our only options are pass (D_PASS), discard (D_DISCARD) or bounce (D_BOUNCE). By setting the $final_*_destiny variables to discard, these messages will be quarantined.

Code: Installing /etc/amavisd.conf
$final_virus_destiny      = D_DISCARD;  # (defaults to D_DISCARD)
$final_banned_destiny     = D_DISCARD;  # (defaults to D_BOUNCE)
$final_spam_destiny       = D_DISCARD;  # (defaults to D_BOUNCE)
$final_bad_header_destiny = D_PASS;  # (defaults to D_PASS), D_BOUNCE suggested
Note: We decided to pass messages with a malformed header. These messages are typically not a security risk, however when we setup the quarantine, we will be sure to capture these for further examination.

In order to ensure we do not get swamped with unnecessary notifications, we will disable the virus_admin and spam_admin notifications. We know we are going to get a tonne of both so why set ourselves up to get an email every time.

Code: Installing /etc/amavisd.conf
# where to send ADMIN VIRUS NOTIFICATIONS (should be a fully qualified address)
# - the administrator envelope address may be a simple fixed e-mail address
#   (a scalar), or may depend on the RECIPIENT address (e.g. its domain).
#   Empty or undef lookup disables virus admin notifications.

$virus_admin = undef;

# $virus_admin = "virusalert\@$mydomain";
# $virus_admin = '';
# $virus_admin = undef;   # do not send virus admin notifications (default)

# equivalent to $virus_admin, but for spam admin notifications:

$spam_admin = undef;

# $spam_admin = "spamalert\@$mydomain";
# $spam_admin = undef;    # do not send spam admin notifications (default)

For now we will set our sender address for notification reports as undefined. You may want to adjust this later when everything is working.

Code: Installing /etc/amavisd.conf
# sender envelope address, from which notification reports are sent from;
# may be a null reverse path, or a fully qualified address:
#   (admin and recip sender addresses default to a null return path).
#   If using strings in double quotes, don't forget to quote @, i.e. \@
# $mailfrom_notify_admin     = "virusalert\@$mydomain";
# $mailfrom_notify_recip     = "virusalert\@$mydomain";
# $mailfrom_notify_spamadmin = "spam.police\@$mydomain";

$mailfrom_notify_admin     = undef;
$mailfrom_notify_recip     = undef;
$mailfrom_notify_spamadmin = undef;

While we will be setting up more sophisticated quaranting later, for now we will setup a simple quarantine directory for testing purposes. Trust me on this, you do not want to get to the end of this and find out it didn’t work because there are way too many steps to roll back and figure out where the mistake was made.

Code: Installing /etc/amavisd.conf
# Location to put infected mail into: (applies to 'local:' quarantine method)
#   empty for not quarantining, may be a file (Unix-style mailbox),
#   or a directory (no trailing slash)
#   (the default value is undef, meaning no quarantine)
$QUARANTINEDIR = "$MYHOME/quarantine";

#$quarantine_subdir_levels = 1;  # add level of subdirs to disperse quarantine

$virus_quarantine_method          = 'local:virus-%m';     # default
$spam_quarantine_method           = 'local:spam-%m.gz';   # default
$banned_files_quarantine_method   = 'local:banned-%m';    # default
$bad_header_quarantine_method     = 'local:badh-%m';      # default

# Separate quarantine subdirectories virus, spam, banned and badh within
# the directory $QUARANTINEDIR may be specified by the following settings
# (the subdirectories need to exist - must be created manually):
#$virus_quarantine_method          = 'local:virus/virus-%m';
#$spam_quarantine_method           = 'local:spam/spam-%m.gz';
#$banned_files_quarantine_method   = 'local:banned/banned-%m';
#$bad_header_quarantine_method     = 'local:badh/badh-%m';

$virus_quarantine_to     = 'virus-quarantine';      # local quarantine
$banned_quarantine_to    = 'banned-quarantine';     # local quarantine
$bad_header_quarantine_to= 'bad-header-quarantine'; # local quarantine
$spam_quarantine_to      = 'spam-quarantine';       # local quarantine

For the rest of this section, just leave it to the default settings.

Section V – Per-recipient and per-sender handling

Leave this section alone for now. We will be handling the per-recipient stuff later.

Section VI – Resource Limits

You shouldn’t need to change any of the settings in this section.

Section VII – External Programs, Virus Scanners

We will jump over most of the stuff in this section and head to the area for the SpamAssassin settings. It is important to know that SpamAssassin will take these settings from the amavisd.conf file and not from its own, so if you need to change them, here is the place. In particular, here are the ones I worked with.

Code: Installing /etc/amavisd.conf
$sa_mail_body_size_limit = 100*1024; # don't waste time on SA if mail is larger
                                     # than 100k (less than 1% of spam is > 64k)
                                     # default: undef, no limitations)
$sa_tag_level_deflt  = 2.0; # add spam info headers if at, or above that level;
                            # undef is interpreted as lower than any spam level

$sa_tag2_level_deflt = 6.31;# add 'spam detected' headers at that level to
                            # passed mail (e.g. when $final_spam_destiny=D_PASS
                            # or for spam_lovers or when below kill_level)

$sa_kill_level_deflt = $sa_tag2_level_deflt; # triggers spam evasive actions
                            # at or above that level: bounce/reject/drop,
                            # quarantine, and adding mail address extension

$sa_dsn_cutoff_level = 9;   # spam level beyond which a DSN is not sent,
                            # effectively turning D_BOUNCE into D_DISCARD;
                            # undef disables this feature and is a default;

# string to prepend to Subject header field when message exceeds tag2 level
$sa_spam_subject_tag = '***SPAM*** ';   # (defaults to undef, disabled)

$sa_spam_modifies_subj = 1; # in @spam_modifies_subj_maps, default is true

# stop anti-virus scanning when the first scanner detects a virus?
#$first_infected_stops_scan = 1;  # default is false, all scanners in a section
                                  # are called
Note: The above levels are what I am using as a starting point. I will fine tune them over time, but for now, it is as good as place as any to start.

The rest of this section can be left as is.

Section VIII – Debugging

All we are going to do here is turn on the SpamAssassin's debugging.

Code: Installing /etc/amavisd.conf
# Turn on SpamAssassin debugging (output to STDERR, use with 'amavisd debug')
$sa_debug = '1,all';  # defaults to false

Section IX – Policy Banks

Finally, the last section and the great news is that there is nothing to do here. Phew!

Initial Testing

To test this we first need to make sure that the amavis userid has the access required to write into the appropriate directory. I did this by changing the owner of the /var/amavis directory, then ran my test.

Code: User Permissions
# chown -R amavis:amavis /var/amavis
# /usr/sbin/amavisd -u amavis debug

Suffice it to say, that you will get a long output at this point showing you what was loaded, what wasn’t etc. Have a read through this and make sure there are no obvious errors such as not being able to write to specific files or directories etc. Beyond that, there isn’t much testing you can do at this point, however if everything worked, it should be up and running and if you run ps –ef from another window (cause the current one will be tied up) you will see the following in the list.

Code: User Permissions
# ps -ef | grep amavis

amavis    6232  6130  9 23:27 pts/0    00:00:08 amavisd (master)
amavis    6243  6232  0 23:27 pts/0    00:00:00 amavisd (virgin child)
amavis    6244  6232  0 23:27 pts/0    00:00:00 amavisd (virgin child)
amavis    6245  6232  0 23:27 pts/0    00:00:00 amavisd (virgin child)
amavis    6246  6232  0 23:27 pts/0    00:00:00 amavisd (virgin child)
root      6393  6368  0 23:27 pts/1    00:00:00 grep amavis
Note: The numbers in the second, third, fourth and fifth rows may be different.

If everything looks good, then hit ctrl-c where you started amavisd from to kill it off.

Configuring Postfix

Amavisd is setup and working, SpamAssassin is in and configured enough to get things going, so now let's configure Postfix so that the mail gets properly routed to be filtered.

In our system, amavisd listens on a TCP port that is bound to the loopback address ( When a mail message is received, Postfix will connect to amavisd and relay the message using SMTP. Amavisd will either reject the message (causing Postfix to bounce it) or accept it, modify it as required and re-inject it by SMTP. To prevent mail loops, Postfix must run a second smtpd daemon bound to a different port on the loopback address. This second smtpd is configured to accept messages and not run the filter and thus the loop is broken.

Let’s go into postfix and define a new mail transport in the file. We will create a Unix service called scan that will use postfix’s smtp command. We will use disable_dns_lookups to save overhead (since it will only relay mail to our loopback IP) and will use maxproc to limit the number of messages that can be simultaneously transported. We will also comment out the original smtp service so we can replace it with one that will deliver to amavisd.

If you are using Postfix 2.0 or later, you can define the spamcheck transport to use Postfix's lmtp command instead of smtp. The LMTP protocol has some advantages over SMTP—notably, LMTP servers (including amavisd) can return individual accept/refuse codes for each message recipient during an LMTP transaction. Postfix's lmtp client can also cache connections to an LMTP server for greater performance.

Define a new mail transport that receives mail from the daemon in This transport will use Postfix's smtpd daemon and is defined by the IP address and port number on which it will listen ( and 10025, respectively). smtpd is an inet service, and many option parameters are provided to prevent further filtering and to restrict access to this mail transport to the local host only. Here is an example of such a definition in

Code: /etc/postfix/
# nano /etc/postfix/

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
# Commented out to support amavisd
#smtp      inet  n       -       n       -       -       smtpd –v
#Instruct smptd on port 25 to deliver to mail amavisd on port 10024
smtp       inet  n       -       n       -       4       smtpd
    -o content_filter=scan:[]:10024
    -o receive_override_options=no_address_mappings
#Mail transport used above.
scan       unix  -       -       n       -       4       lmtp
    -o disable_dns_lookup=yes
    -o lmtp_send_xforward_command=yes
    -o lmtp_data_done_timeout=1200

# Injecting mail back into Postfix after content filter
localhost:10025 inet n  -       n       -       2       smtpd
    -o content_filter=
    -o local_recipient_maps=
    -o relay_recipient_maps=
    -o smtpd_restriction_classes=
    -o smtpd_client_restrictions=
    -o smtpd_helo_restrictions=
    -o smtpd_sender_restrictions=
    -o smtpd_recipient_restrictions=permit_mynetworks,reject
    -o mynetworks=
    -o strict_rfc821_envelopes=yes
    -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_unknown_recipient_checks,no_header_body_checks
    -o smtpd_authorized_xforward_hosts=

smtps       inet  n       -       n       -       4       smtpd
    -o content_filter=scan:[]:10024
    -o receive_override_options=no_address_mappings
    -o smtpd_tls_wrappermode=yes
    -o smtpd_sasl_auth_enable=yes

Note: The scan line specifies that a maximum of four of these processes may run at any time. If you need more concurrency, tune this number but remember to match the numbers with the value of $max_servers in amavisd.conf
Note: the correct service name for secure smtp is "smtps" not "ssmtp"

Some Real Testing

Everything is ready for us to test with some real messages. Let’s just make sure they pass through (no spam identification, etc) and then we can expand the testing to include potential spam. The more complex stuff (white and black lists, etc.) will be covered later.

Start up all the servers (and if they are already running, restart them) so we can load the new settings.

Code: Starting Services
# /etc/init.d/postfix start
* Starting postfix...                                            [ ok ]

# /etc/init.d/amavisd start
* Starting amavisd-new...                                        [ ok ]

With everything running, go to your usual mail client and try sending yourself a few emails to ensure they get through the system and into your mailbox. At this point, everything should get through.

You may also want to do a quick test to ensure that Spam is getting identified. Now you can’t just run off, grab your favourite piece of Spam and expect that Spamassassin will pick it up. There are still many more pieces to this puzzle, so we will use a standard test case that we know will trigger the filter. Send yourself an email with the following text in the body


This will trigger SpamAssassin to quarantine it. If this worked, you should be able to find the message in the /var/amavis/quarantine file.

Note: With amavisd-new version 2.5.2 there is a bunch of virus scanners defined and uncommented in /etc/amavisd.conf. And at this point we havent installed any virus scanners. so the scan will fail. comment out all of the different scanners does the trick. Its found beneth "@av_scanners"
Note: Better solution. Uncomment
 @bypass_virus_checks_maps = (1);
Retrieved from ""

Last modified: Sun, 08 Jun 2008 07:44:00 +0000 Hits: 21,136