DISCLAIMER!

This is a mirror of a guide previously found at http://www200.pair.com/mecham/spam/spamfilter2.html. That copy/instance no longer exists, but I had found the guide quite helpful in the past and have posted it here in case it is a helpful starting point for others in building a personal mail server. Though I've pulled copies of some referenced config files and scripts, some of the info is obviously out of date, so if you use this guide, please do consider it a starting point not a complete start-to-finish guide.

Quick and dirty installation of Postfix, Amavisd-new, SpamAssassin, Razor, DCC, Pyzor and ClamAV on Debian squeeze or Ubuntu 12.04

This document assumes you have a clean fresh installation of Debian squeeze or Ubuntu 12.04 and your machine has a fixed IP address. This document helps you install the software needed to create an anti-virus anti-spam SMTP email relay server. There is no local mail delivery on this box. Inbound virus mail is quarantined and then deleted after 14 days. Spam is tagged and relayed to another server of your choice. If you are interested in setting up Maia Mailguard 1.0.3, you can use this document as a starting point for http://www200.pair.com/mecham/spam/ubuntu1204-maia.html. No support is provided for this document, but if you find a mistake let me know (mr88talent at yahoo dot com). See the disclaimer at http://www200.pair.com/mecham/spam/


I would like you to customize this document by doing a search and replace of the basic elements such as the the IP address of the server this machine will relay mail to and this machine's host name and domain name. I suggest using WordPad or other plain text editor to edit this document. If you have a plain text html editor you like, you may use that instead. Avoid using any editor that modifies the html code. Once you open it in your editor you will see instructions at the top of this document. Please save this web page to your computer, edit it, then open up the copy you have saved.

A few hints on OS installation:
Your machine should have at least 1GB RAM.
If using Ubuntu, install Ubuntu Server 12.04. Here is a starting point http://www.ubuntu.com/download/server/download
If using Debian squeeze, use either the i386 (32-bit) or amd64 (64-bit) netinst CD http://www.debian.org/releases/squeeze/debian-installer/
Note that amd64 CDs are for Intel and AMD processors. The ia64 CDs are only for Itanium processors.
When you get to the point the installer asks for the Hostname:, choose [Go Back] and then Configure network manually.
For the DNS server, please use the IP address of a real DNS server, not your broadband router's gateway address.
I do not choose to install any additional packages in Ubuntu.
In Debian, during tasksel, please deselect [  ] Graphical desktop environment.
In Ubuntu, I run as root during setup. Either run 'sudo su' and then 'cd' before running the install script, or enable root account by entering the following command:
sudo passwd root
This will prompt for your current user's password, then prompt for a new root password, and once you confirm it, you can start using the root account to login.

This is for my benefit, so I can SSH into the box using PuTTY and copy and paste the rest of the commands. Running SSH does have potential security risks. Note also that I'm logged in as root:
apt-get update
apt-get install ssh


Change the default shell from dash to bash:
dpkg-reconfigure dash

Answer: Use dash as the default system shell (/bin/sh)? <No>

Install programs:
apt-get install vim postfix postfix-pcre postfix-mysql postfix-ldap amavisd-new spamassassin razor pyzor clamav clamav-daemon

When Postfix Configuration pops up, answer [Ok] to the first page and the default of [Internet Site] on the second page. Leave the default [System mail name:] on the third page.

If running Ubuntu 12.04:
sed -i "s/ --name \${DAEMONNAME}//" /etc/init.d/amavis

Run sa-update with a script:
cd /usr/sbin
wget https://bamboo.slabnet.com/~hslabbert/spam/sa-update1.sh.txt
mv sa-update1.sh.txt sa-update.sh
chmod +x sa-update.sh
sa-update.sh


I use vim as the default system editor, so I use update-alternatives to change it to /usr/bin/vim.basic
update-alternatives --config editor

Run the update script one a week (Ubuntu may prompt for which editor to use - I use vim.basic):
crontab -e

Change the MM below with a number between 1 and 59 and insert (on the first available blank line):
MM 4 * * 7 /usr/sbin/sa-update.sh

If using Debian, add non-free sources to /etc/apt/sources.list as shown:
vim /etc/apt/sources.list

deb http://mirrors.xmission.com/debian/ squeeze main non-free
deb-src http://mirrors.xmission.com/debian/ squeeze main

deb http://security.debian.org/ squeeze/updates main non-free
deb-src http://security.debian.org/ squeeze/updates main
Install more programs:
apt-get update
apt-get install dnsutils patch flex bison gdb arj unrar nomarch lzop cabextract libimage-info-perl libdbd-mysql-perl libnet-cidr-lite-perl libmail-dkim-perl


unrar (rar) is not free (but you could instead choose to install unrar-free). See http://www.rarsoft.com/index.htm

Other SpamAssassin configuration:
cd /var/lib/amavis
wget https://bamboo.slabnet.com/~hslabbert/spam/sample-spam.txt
sa-learn --spam sample-spam.txt
su amavis -c 'spamassassin <sample-spam.txt'

sed -i "s/#loadplugin Mail::SpamAssassin::Plugin::DKIM/loadplugin Mail::SpamAssassin::Plugin::DKIM/" /etc/spamassassin/v312.pre
sed -i "s/# lock_method flock/lock_method flock/" /etc/spamassassin/local.cf
sed -i "s/# trusted_networks 212.17.35./trusted_networks 192.168.1.0/" /etc/spamassassin/local.cf
su amavis -c 'spamassassin --lint'


Hopefully there were no errors when you ran spamassassin --lint. Add the clamav user to the amavis group and update clamav database:
gpasswd -a clamav amavis
freshclam


Configure Vupil's Razor:
cd
rm /etc/razor/razor-agent.conf
razor-admin -create
razor-admin -create
razor-admin -register
razor-admin -register
sed -i 's/= 3/= 0/' /root/.razor/razor-agent.conf
cp -r /root/.razor /var/lib/amavis
chown -R amavis:amavis /var/lib/amavis
cat /var/lib/amavis/.razor/razor-agent.conf | grep debuglevel


Insure debuglevel was set to 0. Now configure Pyzor:
cd /usr/bin
wget https://bamboo.slabnet.com/~hslabbert/spam/pyzor.deprecation.txt
cp pyzor pyzor.original
patch pyzor <pyzor.deprecation.txt
pyzor discover
su amavis -c 'pyzor discover'
pyzor ping
su amavis -c 'pyzor ping'


Install DCC. DCC detects messages that are bulk mailed. The way I understand the license for DCC is if you are not a reseller and you receive less than somewhere around 100,000 messages per day, you can use the DCC client by itself. If you run a larger site you need to run a DCC server and flood your data to public DCC servers. If you are a reseller you need to pay for a license. I only outline installing a client. Be patient when compiling the program - it may appear to hang at one point:
cd /usr/local/src
wget http://www.rhyolite.com/anti-spam/dcc/source/old/dcc-1.3.140.tar.Z
tar xzf dcc-1.3.140.tar.Z
cd dcc-1.3.140
./configure --with-uid=amavis && make && make install

ln -s /var/dcc/libexec/cron-dccd /usr/bin/cron-dccd
chown -R amavis:amavis /var/dcc
cdcc info


You should see at least some entries with 'requests ok' (127.0.0.1 is expected to fail). If not, then you might be having an issue with a proxy server or firewall. http://www.dcc-servers.net/dcc/firewall.html gives an example of a Cisco router access list entry. Keep in mind that you will likely not run a DCC server, only the DCC client. If the tests fail you won't be able to test again until some period of time has passed. The program does this to prevent broken clients from trying too often.
crontab -e

and insert (at the bottom of existing entries):
43 11 * * * /usr/bin/cron-dccd

cd
echo "dcc_home /var/dcc" >> /etc/spamassassin/local.cf
sed -i 's/DCCIFD_ENABLE=off/DCCIFD_ENABLE=on/' /var/dcc/dcc_conf
sed -i 's/DBCLEAN_LOGDAYS=14/DBCLEAN_LOGDAYS=1/' /var/dcc/dcc_conf
sed -i 's/DCCIFD_LOGDIR/#DCCIFD_LOGDIR/' /var/dcc/dcc_conf
cp /var/dcc/libexec/rcDCC /etc/init.d/adcc
sed -i 's/# Default-Start:     3 5/# Default-Start:     2 3 4 5/' /etc/init.d/adcc
sed -i 's/# Default-Stop:/# Default-Stop:      0 1 6/' /etc/init.d/adcc
update-rc.d adcc defaults
/etc/init.d/adcc start
sed -i 's/#loadplugin Mail::SpamAssassin::Plugin::DCC/loadplugin Mail::SpamAssassin::Plugin::DCC/' /etc/spamassassin/v310.pre
/etc/init.d/amavis restart


You will get errors because I chose to turn of logging. I wouldn't worry about them:
Jun 15 19:33:12 sfa dccifd[7459]: log thresholds set with -t but no -l directory
Jun 15 19:33:12 sfa dccifd[7459]: no -l directory prevents per-user logging with -U

Test with spamassassin:
cd /var/lib/amavis
su amavis -c 'spamassassin -D dcc <sample-spam.txt'


You should get something like this in the output:
[7615] dbg: dcc: dccifd is available: /var/dcc/dccifd
[7615] dbg: dcc: dccifd got response: X-DCC--Metrics: sfa 1049; Body=many Fuz1=many Fuz2=many


Read the LICENSE:
cat /usr/local/src/dcc-1.3.140/LICENSE


Optionally use additional anti-phishing and scam signatures. This setup uses Bill Landry's clamav-unofficial-sigs.sh script, version 3.7.1:
apt-get install curl rsync

cd /usr/share/man/man8/
wget https://bamboo.slabnet.com/~hslabbert/spam/clamav-unofficial-sigs.8.gz.b
mv clamav-unofficial-sigs.8.gz.b clamav-unofficial-sigs.8.gz
cd /etc
wget https://bamboo.slabnet.com/~hslabbert/spam/clamav-unofficial-sigs.conf.txt
mv clamav-unofficial-sigs.conf.txt clamav-unofficial-sigs.conf
cd /usr/sbin
wget https://bamboo.slabnet.com/~hslabbert/spam/clamd-status.sh.txt
mv clamd-status.sh.txt clamd-status.sh
chmod +x clamd-status.sh
wget https://bamboo.slabnet.com/~hslabbert/spam/clamav-unofficial-sigs.sh.txt
mv clamav-unofficial-sigs.sh.txt clamav-unofficial-sigs.sh
chmod +x clamav-unofficial-sigs.sh
cd /etc/logrotate.d
wget https://bamboo.slabnet.com/~hslabbert/spam/clamav-unofficial-sigs-logrotate.txt
mv clamav-unofficial-sigs-logrotate.txt clamav-unofficial-sigs
clamav-unofficial-sigs.sh


The script will execute. When it's finished:

ls -l /var/lib/clamav

You will notice the data has been downloaded:
-rw-r--r-- 1 clamav clamav    11478 2010-06-06 19:52 bytecode.cvd
-rw-r--r-- 1 clamav clamav  2196135 2010-06-06 19:52 daily.cvd
-rw-r--r-- 1 clamav clamav    50400 2010-05-27 08:12 honeynet.hdb
-rw-r--r-- 1 clamav clamav  3950238 2010-06-05 08:49 junk.ndb
-rw-r--r-- 1 clamav clamav   559557 2010-06-06 19:49 jurlbl.ndb
-rw-r--r-- 1 clamav clamav 22906487 2010-06-06 19:52 main.cvd
-rw-r--r-- 1 clamav clamav       52 2010-06-06 19:52 mirrors.dat
-rw-r--r-- 1 clamav clamav  2299236 2010-06-06 09:50 phish.ndb
-rw-r--r-- 1 clamav clamav    60727 2010-06-05 08:49 rogue.hdb
-rw-r--r-- 1 clamav clamav     1116 2010-05-13 04:49 sanesecurity.ftm
-rw-r--r-- 1 clamav clamav  1712518 2010-06-04 12:54 scam.ndb
-rw-r--r-- 1 clamav clamav    75068 2010-05-27 08:12 securiteinfobat.hdb
-rw-r--r-- 1 clamav clamav   193234 2010-05-27 08:12 securiteinfodos.hdb
-rw-r--r-- 1 clamav clamav    53913 2010-05-27 08:12 securiteinfoelf.hdb
-rw-r--r-- 1 clamav clamav   225919 2010-06-02 02:53 securiteinfo.hdb
-rw-r--r-- 1 clamav clamav  1329399 2010-06-03 02:26 securiteinfohtml.hdb
-rw-r--r-- 1 clamav clamav   244083 2010-05-27 08:13 securiteinfooffice.hdb
-rw-r--r-- 1 clamav clamav   238856 2010-05-27 08:13 securiteinfopdf.hdb
-rw-r--r-- 1 clamav clamav    21106 2010-05-27 08:13 securiteinfosh.hdb
-rw-r--r-- 1 clamav clamav    52755 2010-06-06 09:50 spamimg.hdb
-rw-r--r-- 1 clamav clamav   866409 2010-06-06 19:45 winnow_malware.hdb
-rw-r--r-- 1 clamav clamav   785121 2010-06-06 19:45 winnow_malware_links.ndb
Now we add a crontab entry with download attempts performed every 6th hour:
crontab -e

Insert these two entries. Replace MM (minutes) below with a number between 1 and 59:
MM */6 * * * /usr/sbin/clamav-unofficial-sigs.sh
*/6 * * * * /usr/sbin/clamd-status.sh


Save and exit the file. The first cron job should run every 6 hours, and the second, every 6 minutes. The clamav-status.sh script will restart clamd after a crash. There is a log file at /var/log/clamav-unofficial-sigs.log and you can read the man page at 'man clamav-unofficial-sigs'. Amavisd-new can treat SaneSecurity/MSRBL/securiteinfo 'viruses' as spam. You just need to add some SpamAssassin rules so they score more than 0.1:
cd /etc/spamassassin
wget https://bamboo.slabnet.com/~hslabbert/spam/amavis-sanesecurity_v2.cf
spamassassin --lint
amavisd-new reload



Turn on spam and virus filtering for amavisd-new:
sed -i "s/#@bypass_virus/ @bypass_virus/" /etc/amavis/conf.d/15-content_filter_mode
sed -i "s/#   \\\%bypass_virus/    \\\%bypass_virus/" /etc/amavis/conf.d/15-content_filter_mode
sed -i "s/#@bypass_spam/ @bypass_spam/" /etc/amavis/conf.d/15-content_filter_mode
sed -i "s/#   \\\%bypass_spam/    \\\%bypass_spam/" /etc/amavis/conf.d/15-content_filter_mode
cat /etc/amavis/conf.d/15-content_filter_mode


The lines should be uncommented. Now edit the main amavisd-new configuration file /etc/amavis/conf.d/50-user and add these entries in the middle of the file (must be between "use strict;" and "1;")
vim /etc/amavis/conf.d/50-user
# ----------------------------------
# nice to have $log_level (1-5) available:
$log_level = 0;

# explicitly set $mydomain, $myhostname and @local_domains_maps:
# see /usr/share/doc/amavisd-new/examples/amavisd.conf-sample.gz
$mydomain = 'example.com';
$myhostname = 'sfa.example.com';
@local_domains_maps = ( ['.example.com'] );

# when amavisd-new sends notifications, they appear to come from here:
$mailfrom_notify_admin     = "postmaster\@$mydomain";
$mailfrom_notify_recip     = "postmaster\@$mydomain";
$mailfrom_notify_spamadmin = "postmaster\@$mydomain";
$hdrfrom_notify_sender = "amavisd-new <postmaster\@$mydomain>";

# Set number of processes. Rough guide for dual processor, 1GB = 6, 2GB = 12, 4GB = 24
# You MUST also change maxproc for the smtp-amavis transport to match this number, e.g:
# smtp-amavis unix -      -       n       -       2  smtp
$max_servers = 2;

# Note: If you need to set maxproc > 20, instead use:
# smtp-amavis_destination_concurrency_limit = <number>
# in /etc/postfix/main.cf and set the smtp-amavis transport like so:
# smtp-amavis unix -      -       n       -       -  smtp

# We discard (and quarantine) viruses, pass spam, 
# bounce (and quarantine) banned files and pass bad headers:
$final_virus_destiny      = D_DISCARD;
$final_banned_destiny     = D_DISCARD;
$final_spam_destiny       = D_PASS;
$final_bad_header_destiny = D_PASS;

# disable DKIM for now
$enable_dkim_verification = 0;  # enable DKIM signatures verification
$enable_dkim_signing = 0;    # load DKIM signing code, keys defined by dkim_key

# don't quarantine bad headers (no need since we pass them all):
$bad_header_quarantine_to = undef;

# Spam gets the Subject line prepended with:
$sa_spam_subject_tag = 'Spam> ';

# We tag all headers (for 'local' domains) with X-Spam info:
$sa_tag_level_deflt = undef;

# This is the system default spam tag level
$sa_tag2_level_deflt = 6.31;

# The default is to not quarantine any spam 
$sa_kill_level_deflt = 9999;

# We will quarantine viruses to /var/lib/amavis/virusmails (the default).
# We will use a cron job /etc/cron.daily/rmvirusquar to automatically delete these
# files older than 14 days from the quarantine. We can use amavisd-release to release
# quarantined messages. 

# disable quarantine subdirectories
$quarantine_subdir_levels = undef;

# more recent version
@virus_name_to_spam_score_maps =
 (new_RE(  # the order matters!
   [ qr'^Structured\.(SSN|CreditCardNumber)\b'            => 0.1 ],
   [ qr'^(Heuristics\.)?Phishing\.'                       => 0.1 ],
   [ qr'^(Email|HTML)\.Phishing\.(?!.*Sanesecurity)'      => 0.1 ],
   [ qr'^Sanesecurity\.(Malware|Rogue|Trojan)\.' => undef ],# keep as infected
   [ qr'^Sanesecurity\.'                                  => 0.1 ],
   [ qr'^Sanesecurity_PhishBar_'                          => 0   ],
   [ qr'^Sanesecurity.TestSig_'                           => 0   ],
   [ qr'^Email\.Spam\.Bounce(\.[^., ]*)*\.Sanesecurity\.' => 0   ],
   [ qr'^Email\.Spammail\b'                               => 0.1 ],
   [ qr'^MSRBL-(Images|SPAM)\b'                           => 0.1 ],
   [ qr'^VX\.Honeypot-SecuriteInfo\.com\.Joke'            => 0.1 ],
   [ qr'^VX\.not-virus_(Hoax|Joke)\..*-SecuriteInfo\.com(\.|\z)' => 0.1 ],
   [ qr'^Email\.Spam.*-SecuriteInfo\.com(\.|\z)'          => 0.1 ],
   [ qr'^Safebrowsing\.'                                  => 0.1 ],
   [ qr'^winnow\.(phish|spam)\.'                          => 0.1 ],
   [ qr'^INetMsg\.SpamDomain'                             => 0.1 ],
   [ qr'^Doppelstern\.(Scam4|Phishing|Junk)'              => 0.1 ],
   [ qr'^ScamNailer\.'                                    => 0.1 ],
   [ qr'^HTML/Bankish'                                    => 0.1 ],  # F-Prot
   [ qr'-SecuriteInfo\.com(\.|\z)'         => undef ],  # keep as infected
   [ qr'^MBL_NA\.UNOFFICIAL'               => 0.1 ],    # false positives
   [ qr'^MBL_'                             => undef ],  # keep as infected
 ));

# ---------------------------------- 
# ----------------------------------
Restart clamav-daemon and amavisd-new:
/etc/init.d/amavis stop
/etc/init.d/clamav-daemon stop
/etc/init.d/clamav-daemon start
/etc/init.d/amavis start


I quarantine viruses locally in /var/lib/amavis/virusmails for 14 days, then delete them. If you would like to accomplish this deletion task, grab the script from me:
cd /etc/cron.daily
wget https://bamboo.slabnet.com/~hslabbert/spam/rmvirusquar.txt
mv rmvirusquar.txt rmvirusquar
chmod +x rmvirusquar
./rmvirusquar


Basic Postfix configuration. Postfix is configured to send mail to amavisd-new.
Amavisd-new returns it to Postfix and then Postfix relays it on to 192.168.1.223 which I hope is the server you use to store user's mail.
Please refrain from running these commands more than once:
postfix stop
cd /etc/postfix
cp -i main.cf main.cf-original
cp -i master.cf master.cf-original

wget https://bamboo.slabnet.com/~hslabbert/spam/amavis-transport.txt
grep 'smtp-amavis' master.cf || cat amavis-transport.txt >> master.cf
sed -i '/pickup/s|$| -o content_filter= |' /etc/postfix/master.cf

postconf -e "mydomain = example.com"
postconf -e "myorigin = example.com"
postconf -e "myhostname = sfa.example.com"
postconf -e "mydestination = "
postconf -e "recipient_delimiter = "
postconf -e "relayhost = "
postconf -e "local_transport = error:no local mail delivery"
postconf -e "local_recipient_maps = "
postconf -e "virtual_alias_maps = hash:/etc/postfix/virtual"
touch /etc/postfix/virtual
postmap /etc/postfix/virtual
postconf -e "relay_domains = example.com"
postconf -e "smtpd_helo_required = yes"
postconf -e "smtpd_data_restrictions = reject_unauth_pipelining"
postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination"
touch /etc/postfix/transport
cp /etc/postfix/transport /etc/postfix/transport-original
postconf -e "transport_maps = hash:/etc/postfix/transport"
echo "example.com relay:[192.168.1.223]" >> /etc/postfix/transport
postmap /etc/postfix/transport
postconf -e "content_filter = smtp-amavis:[127.0.0.1]:10024"
/etc/init.d/postfix restart


Time to test. Configure a mail client (MUA) to use this this box as its SMTP server and send a message through this machine to test:
tail -f /var/log/mail.log

Here is success for me (sending from 192.168.1.41):
Feb 5 19:52:18 sfa postfix/smtpd[1174]: connect from unknown[192.168.1.41]
Feb 5 19:52:18 sfa postfix/smtpd[1174]: E072E143F: client=unknown[192.168.1.41]
Feb 5 19:52:18 sfa postfix/cleanup[1176]: E072E143F: message-id=<1186200959.20110205195214@example.net>
Feb 5 19:52:19 sfa postfix/qmgr[1079]: E072E143F: from=<gary@example.net>, size=583, nrcpt=1 (queue active)
Feb 5 19:52:19 sfa postfix/smtpd[1174]: disconnect from unknown[192.168.1.41]
Feb 5 19:52:27 sfa postfix/smtpd[1165]: connect from localhost[127.0.0.1]
Feb 5 19:52:27 sfa postfix/smtpd[1165]: 8BA711440: client=localhost[127.0.0.1]
Feb 5 19:52:27 sfa postfix/cleanup[1176]: 8BA711440: message-id=<1186200959.20110205195214@example.net>
Feb 5 19:52:27 sfa postfix/qmgr[1079]: 8BA711440: from=<gary@example.net>, size=1202, nrcpt=1 (queue active)
Feb 5 19:52:27 sfa amavis[883]: (00883-01) Passed CLEAN, LOCAL [192.168.1.41] [192.168.1.41] <gary@example.net> -> <garyv@example.com>, Message-ID: <1186200959.20110205195214@example.net>, mail_id: IF3C-rg74z+S, Hits: 2.752, size: 583, queued_as: 8BA711440, 8693 ms
Feb 5 19:52:27 sfa postfix/smtp[1177]: E072E143F: to=<garyv@example.com>, relay=127.0.0.1[127.0.0.1]:10024, delay=8.8, delays=0.1/0.02/0.03/8.7, dsn=2.0.0, status=sent (250 2.0.0 Ok, id=00883-01, from MTA([127.0.0.1]:10025): 250 2.0.0 Ok: queued as 8BA711440)
Feb 5 19:52:27 sfa postfix/qmgr[1079]: E072E143F: removed
Feb 5 19:52:29 sfa postfix/smtp[1181]: 8BA711440: to=<garyv@example.com>, relay=192.168.1.223[192.168.1.223]:25, delay=1.7, delays=0.09/0.04/0.93/0.59, dsn=2.0.0, status=sent (250 2.0.0 Ok: queued as 2361118D68)
Feb 5 19:52:29 sfa postfix/qmgr[1079]: 8BA711440: removed


Recipient verification for a Postfix relay server


Postfix offers two different ways of figuring out if a recipient's address is valid or not. In one method, you supply a list of valid recipients, and Postfix looks up the address in that table. If a recipient is not in the table, the mail is rejected. In the second method, Postfix probes the downstream server prior to accepting the message. If the downstream server rejects the attempt to send a message to a particular recipient, then Postfix will also reject the message. The second method only works when the downstream server in fact does immediately reject mail to invalid users (not all do). In my example below, I use a combination of both.

When you configure a Postfix email server as a firewall for another server (or servers), by default Postfix has no knowlege of whom the valid recipients are. Let's say we have two domains we relay mail to, one (example.net) relays mail to a server running sendmail and the other (example.org) relays mail to a server running Microsoft Exchange 2000. Our Postfix relay server will accept mail addressed to any recipient in either domain. This particular sendmail server is configured to immediately reject mail to invalid users. When it rejects a message, Postfix will create a bounce notice and attempt to deliver it to the sender. If the sender is completely bogus, the message will sit in our deferred queue for days while delivery attempts are made. If the sender is faked but points to a real address, then we are spamming an innocent victim. This victim is getting "joe jobbed" - and we are facilitating it - and now we are a source of backscatter. If we send a message to the domain that forwards to the sendmail server, we can see from the bounce notice that the downstream server (at the hypothetical address of 777.777.777.777) rejected it:
<testgarbage@example.net>: host msa.example.com[111.111.111.111] said: 550
    <testgarbage@example.net>: Recipient address rejected: undeliverable
    address: host 777.777.777.777[777.777.777.777] said: 550 5.1.1
    <testgarbage@example.net>... User unknown (in reply to RCPT TO command)
    (in reply to RCPT TO command)
In this case (the case being the downstream server immediately rejects mail to invalid users) we can use either address verification or relay_recipient_maps. Basically, with address verification (reject_unverified_recipient), Postfix first checks the downstream server to see if it will accept a message to the recipient or not, prior to accepting the message. If the downstream server rejects the message (due to invalid address), so will Postfix (before it accepts the message). On the other hand, if you use relay_recipient_maps, relay_recipient_maps requires that all known good recipient addresses (for the domains listed in relay_domains) are in a lookup table. Mail addressed to a recipient whose domain is listed in relay_domains that is not also listed in the table defined in relay_recipient_maps is rejected.

If we relay a message to the Exchange server, the particular Exchange server in our example accepts the message and later generates a bounce notice which it mails to the sender. We can only use relay_recipient_maps in this case where the downstream server does not immediately reject messages addressed to invalid users. Let's continue by first setting up address verification for example.net (the domain using the sendmail server). I will illustrate this mixed setup in which both reject_unverified_recipient and relay_recipient_maps will be utilized. Keep in mind that for your setup, you will have to discover which relay domains of yours will immediately reject mail to invalid users and which will not (if you want to use reject_unverified_recipient that is). Keep in mind that use of reject_unverified_recipient is a convenience over use of relay_recipient_maps, but the ideal situation would be exclusive use of relay_recipient_maps for all your relay domains. The question is - who will maintain the adding and deleting of email addresses?
vim /etc/postfix/verify_domains

and insert the domain(s) that use server(s) that will immediately reject mail to unknown users:
example.net reject_unverified_recipient

then postmap it:
postmap /etc/postfix/verify_domains

vim /etc/postfix/main.cf , make smtpd_recipient_restrictions pretty like this and add the red item in the position shown. Read the beginning (half dozen lines or so) of http://www.postfix.org/postconf.5.html:
smtpd_recipient_restrictions = 
    permit_mynetworks,
    reject_unauth_destination,
    reject_unlisted_recipient,
    check_recipient_access hash:/etc/postfix/verify_domains
Also add these items to main.cf:
address_verify_map = btree:/var/lib/postfix/verify_cache
# Uncomment this next line when finished testing address verification:
#unverified_recipient_reject_code = 550


postfix reload and test the setup (by sending mail to invalid users in the relay domain and reading the log). When finished testing, uncomment the unverified_recipient_reject_code line in main.cf.

With the other domain (example.org on the Exchange server) we need to set up relay_recipient_maps. We will create a file called /etc/postfix/relay_recipients. Once relay_recipient_maps is configured in main.cf, any recipient or recipient domain of a relay domain not listed in /etc/postfix/relay_recipients will get rejected. This means /etc/postfix/relay_recipients must contain every single recipient of every single relay domain this machine accepts mail for. However, domain wildcards can be used as placeholders. In our example case, example.net is already using address verification to reject mail to invalid users, so we can use a wildcard for that domain: @example.net. For example.org we can start out with a wildcard while we are in the process of gathering valid addresses:
vim /etc/postfix/relay_recipients

@example.net 1
@example.org 1


but once we have all the address gathered, we need to get rid of the wildcard:
@example.net 1
#@example.org 1
user1@example.org 1
user2@example.org 1
user3@example.org 1
user4@example.org 1


Then of course:
postmap /etc/postfix/relay_recipients

The "1" after each entry could be something else, like "OK" or something. It's not important what the actual text is because it's not used for anything, but it must exist. To set up relay_recipient_maps:
postconf -e "relay_recipient_maps = hash:/etc/postfix/relay_recipients"
postfix reload


Don't forget to uncomment #unverified_recipient_reject_code = 550 once address verification is known to work as expected (assuming you are using reject_unverified_recipient via the verify_domains map). Then test, test, test. If you are using Exchange, there are some HOWTOs out there that may help with pulling data out of Active Directory:
http://www200.pair.com/mecham/spam/relay_recipients.html
http://www200.pair.com/mecham/spam/postfix_exchange.html
http://www.unixwiz.net/techtips/postfix-exchange-users.html
http://postfix.state-of-mind.de/patrick.koetter/mailrelay/
http://www200.pair.com/mecham/spam/PostfixAddressExtract.vbs.txt

If you use Maia Mailguard, you can create a script that uses the Maia database to create the relay_recipients file. Here is a sample:
#!/bin/bash
# Extract e-mail addresses from Maia
mysql -umaia -pmaia_password maia -B -N -e "SELECT concat(email,' 1') FROM users" >/etc/postfix/relay_recipients.tmp1
# Remove any domain only addresses and check for duplicates
egrep -v ^@ /etc/postfix/relay_recipients.tmp1 | sort -u >/etc/postfix/relay_recipients.tmp
# If desired, add domains that CAN use reject_unlisted_recipient
echo "example.net 1" >> /etc/postfix/relay_recipients.tmp
echo "example.org 1" >> /etc/postfix/relay_recipients.tmp
# Make sure the file contains at least one valid address, otherwise exit
if ! grep -q garyv@example.com /etc/postfix/relay_recipients.tmp; then
  echo "there was a problem creating the relay_recipients map" 2>&1
  exit 1
else
  cp /etc/postfix/relay_recipients.tmp /etc/postfix/relay_recipients
  postmap /etc/postfix/relay_recipients.tmp
  cp /etc/postfix/relay_recipients.tmp.db /etc/postfix/relay_recipients.db
fi



OPTIONAL: install Postgrey

Of course Greylisting will reject a ton of spam - but at a cost. Some mail will be delayed. To help out I cut the retry time from 6 minutes to 29 seconds. We will use 'selective greylisting' with the idea being we will attempt to only target clients that appear to come from dial-up and dynamic IP addresses. If you decide to implement this I also recommend you whitelist clients you trust. The whitelists are located in the /etc/postgrey/ directory. We can also whitelist senders, clients or networks in Postfix.
apt-get install postgrey

sed -i 's/--inet=127.0.0.1:60000/--inet=127.0.0.1:60000 --delay=29/' /etc/default/postgrey
sed -i 's/--inet=10023/--inet=127.0.0.1:60000 --delay=29/' /etc/default/postgrey
/etc/init.d/postgrey restart
ps aux | grep postgrey | grep -v grep

cd /etc/postfix
wget https://bamboo.slabnet.com/~hslabbert/spam/check_client_fqdn
cp -ip main.cf main.cf-before-grey

postconf -e "smtpd_restriction_classes = check_greylist"
postconf -e "check_greylist = check_policy_service inet:127.0.0.1:60000"
touch greylist_sender_exceptions
postmap greylist_sender_exceptions
touch cidr_greylist_network_exceptions


In main.cf, our smtpd_recipient_restrictions should already look pretty like the sample below. Read the beginning (half dozen lines or so) of http://www.postfix.org/postconf.5.html. Now we need to add greylisting and a sender whitelist for greylisting.
Either vim /etc/postfix/main.cf or use the WinSCP editor and edit smtpd_recipient_restrictions, adding the items in red:
smtpd_recipient_restrictions =
    permit_mynetworks,
    permit_sasl_authenticated,
    reject_unauth_destination,
    check_recipient_access hash:/etc/postfix/greylist_sender_exceptions,
    check_client_access cidr:/etc/postfix/cidr_greylist_network_exceptions,
    check_client_access pcre:/etc/postfix/check_client_fqdn
Once the file is saved:
postfix reload
tail -f /var/log/mail.log


Set your MUA to deliver normally to port 25 (no authentication) and then attempt to send a message through and see if your client gets greylisted like mine did. If I wait 30 seconds I should be able to send the message. Note that my client IP is not included in mynetworks in main.cf (if it were, everything in smtpd_recipient_restrictions would be bypassed):
Jun 15 21:12:19 sfa postfix/smtpd[10095]: NOQUEUE: reject: RCPT from
 unknown[192.168.1.41]: 450 4.7.1 <unknown[192.168.1.41]>: Client host rejected:
  Greylisted, see http://isg.ee.ethz.ch/tools/postgrey/help/example.com.html;
   from=<gary@example.net> to=<garyv@example.com> proto=ESMTP helo=<nobody>


I am going to whitelist 192.168.1.0 so clients on my internal network are not subjected to greylisting. Note that typically you would simply add your internal network to the mynetworks setting in main.cf. Cidr type tables do not need to be postmapped. http://www.postfix.org/cidr_table.5.html. I will place the whitelist entry in
vim /etc/postfix/cidr_greylist_network_exceptions
192.168.0.0/16 OK

postfix reload

I am going to wipe out the entire Postgrey database in order to prove to myself that my change did in fact whitelist my network. You don't want to do this once the system is up and running (unless the database becomes corrupt). Then I am going to try to send another message.

ls -l /var/lib/postgrey/
/etc/init.d/postgrey stop
rm /var/lib/postgrey/*
/etc/init.d/postgrey start

ls -l /var/lib/postgrey/


tail -f /var/log/mail.log

And it worked:
Jun 15 21:36:51 sfa postfix/smtpd[10167]: connect from unknown[192.168.1.41]
Jun 15 21:36:52 sfa postfix/smtpd[10167]: 1EDD624218: client=unknown[192.168.1.41]



OPTIONAL Install Bind9

A local caching name server can improve performance of the server.
apt-get install bind9
/etc/init.d/bind9 stop
touch /var/cache/bind/managed-keys.bind
chown bind:bind /var/cache/bind/managed-keys.bind
/etc/init.d/bind9 start


Now edit /etc/resolv.conf. Change the IP address of the nameserver to be the IP address of this box. Place the current nameserver below what is now your primary server:
vi /etc/resolv.conf

The result would look similar to this:
search example.com
nameserver 192.168.1.222
nameserver 68.87.85.98


If /etc/resolv.conf shows "DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN" you should instead edit the name servers in /etc/network/interfaces under the "dns-nameservers" setting (separated by spaces) and then restart the network:
/etc/init.d/networking restart

So far we have set up bind9 as a local caching only name server. Here we add an additional security measure that prevents unauthorized machines from using our name server:
vi /etc/bind/named.conf.options

On the line below "directory" we want to add a line that restricts use of our name server to the network our mailserver is on. Place a [Tab] in front of the entry so it lines up with the other entries. You can add more than one network here if you like. Place a ";" (semicolon) after each network. The network address shown is only an example.
allow-query {192.168.1.0/24;};

We need to suppress some logging:
vi /etc/bind/named.conf

And insert (below the first set of comments}:
logging {
category lame-servers {null; };
category edns-disabled { null; };
};


Save and exit the file, then I would restart bind9 and check that it is running:
/etc/init.d/bind9 restart
lsof -i | grep :domain


We will get a number of 'RFC 1918 response' related entries in our log unless we include the RFC 1918 zones:
vi /etc/bind/named.conf.local

Remove the comment marks "// " from the beginning of this option:
// include "/etc/bind/zones.rfc1918";

Then restart bind9 and check for errors:
/etc/init.d/bind9 restart
lsof -i | grep :domain
tail -27 /var/log/syslog


We added a new user to /etc/passwd so give Postfix a copy:
/etc/init.d/postfix restart

Check that we can resolve names:
dig yahoo.com

In the ANSWER SECTION you should see some A records with IP addresses and near the bottom the SERVER: should be this server.

Run a quick speed test:
cd /var/lib/amavis
wget https://bamboo.slabnet.com/~hslabbert/spam/sample-spam.txt
time su amavis -c 'spamassassin <sample-spam.txt'


Typically on a machine with a decent CPU and adequate Internet bandwidth you can expect times between 2 and 6 seconds. Make a note of the 'real' time. Run it again, and average the two times:
time su amavis -c 'spamassassin <sample-spam.txt'

vim /etc/resolv.conf and temporarily comment out the IP address of the local machine (so you are using your ISP's name server).
Use dig yahoo.com to check which SERVER: is actually used.

Run the speed test again twice and average the times again:
time su amavis -c 'spamassassin <sample-spam.txt'
time su amavis -c 'spamassassin <sample-spam.txt'


If your response time is faster when using your ISP's server than it is when using your local Bind server, then you should probably vim /etc/resolv.conf again and reset your local machine as the primary DNS server - but configure Bind to forward requests to your ISP's server (or other reliable server):

Optionally configure bind9 as a forwarding server. Bind9 as we have it configured now will first query the root servers for hints when needed. You may prefer to forward queries to another DNS server instead. There are advantages and disadvantages in doing this. There are two main reasons not to use forwarding. The first is that ClamAV uses DNS to check for updates. It's possible that if you are relying on the forwarding DNS server to check the ClamAV DNS server, the information may not be up to date. The other reason has to do with spamhaus.org's DSNBLs. If you use someone else's (your ISP's) DNS server, all the queries from that DNS server are counted toward the total number of connections to the spamhaus servers, which may result in you loosing that free service. It is absolutely imperative that any name servers you use as forwarders are known to work from our mailserver. These will almost certainly be the primary and secondary servers you previously configured in /etc/resolv.conf, or your ISP's servers (not 127.0.0.1, and not the IP address of the local machine). They should point to real name servers and not a DNS proxy like a Linksys broadband router or other gateway device (unless that proxy does not allow proper access to real name servers outside your network - which is sometimes the case). Add the 'forwarders' entry just below the 'allow-query' entry we just made:
vi /etc/bind/named.conf.options
and add:
forwarders {444.444.444.444; 555.555.555.555;};
To never query the root servers, optionally add:
forward only;

Then restart bind9 and check for errors again:
/etc/init.d/bind9 restart
lsof -i | grep :domain
tail -55 /var/log/syslog


Feel free run the speed test again. If setting your local Bind server as the nameserver in /etc/resolv.conf is consistently slower than using your ISP's DNS server (even when using forwarding), then maybe you should simply use your ISP's name server.

Remember: If the result of cat /etc/resolv.conf shows "DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN" you should instead edit the name servers in /etc/network/interfaces under the "dns-nameservers" setting (separated by spaces) and then restart the network. If you do not do it this way, changes to resolv.conf will be lost after a reboot:
/etc/init.d/networking restart

Here is another possibly useful test:
cd /var/lib/amavis
su amavis -c 'spamassassin -t -D <sample-spam.txt' 2>&1 | perl -MPOSIX -MTime::HiRes -n -e 'BEGIN {$|=1; $dp=0; $t0=Time::HiRes::time}; $t=Time::HiRes::time; $dt=$t-$t0; printf("%s%06.3f %4.3f %4.3f %s", POSIX::strftime("%H:%M:",localtime($t)), $t-int($t/60)*60, $dt, $dt-$dp, $_); $dp=$dt' $* >0.log 2>&1 &


Wait a full minute, then read the debug file:

less 0.log

You should be able to see where the most time is spent. The second column is how much time has past, third is how much time is spent on one particular operation.


mr88talent at yahoo dot com
25 JUN 2011